sebavan 6 년 전
부모
커밋
e09a76cfb7
63개의 변경된 파일13375개의 추가작업 그리고 9764개의 파일을 삭제
  1. 7127 4227
      Playground/babylon.d.txt
  2. 4047 4022
      dist/preview release/babylon.d.ts
  3. 1 1
      dist/preview release/babylon.js
  4. 53 2
      dist/preview release/babylon.max.js
  5. 53 2
      dist/preview release/babylon.no-module.max.js
  6. 1 1
      dist/preview release/babylon.worker.js
  7. 53 2
      dist/preview release/es6.js
  8. 100 75
      dist/preview release/gui/babylon.gui.d.ts
  9. 1 1
      dist/preview release/gui/babylon.gui.js
  10. 1 1
      dist/preview release/gui/babylon.gui.min.js
  11. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  12. 215 169
      dist/preview release/gui/babylon.gui.module.d.ts
  13. 8 8
      dist/preview release/inspector/babylon.inspector.bundle.js
  14. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  15. 1 1
      dist/preview release/inspector/babylon.inspector.d.ts
  16. 2 2
      dist/preview release/inspector/babylon.inspector.module.d.ts
  17. 1 1
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  18. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  19. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  20. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  21. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  22. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  23. 1 1
      dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js
  24. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  25. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  26. 7 17
      dist/preview release/viewer/babylon.viewer.d.ts
  27. 3 3
      dist/preview release/viewer/babylon.viewer.js
  28. 7 7
      dist/preview release/viewer/babylon.viewer.max.js
  29. 8 21
      dist/preview release/viewer/babylon.viewer.module.d.ts
  30. 2 0
      dist/preview release/what's new.md
  31. 7 11
      gui/src/2D/advancedDynamicTexture.ts
  32. 6 1
      gui/src/2D/controls/button.ts
  33. 32 34
      gui/src/2D/controls/checkbox.ts
  34. 35 38
      gui/src/2D/controls/colorpicker.ts
  35. 85 47
      gui/src/2D/controls/container.ts
  36. 147 72
      gui/src/2D/controls/control.ts
  37. 2 3
      gui/src/2D/controls/displayGrid.ts
  38. 37 5
      gui/src/2D/controls/grid.ts
  39. 51 35
      gui/src/2D/controls/image.ts
  40. 4 4
      gui/src/2D/controls/index.ts
  41. 111 115
      gui/src/2D/controls/inputText.ts
  42. 9 11
      gui/src/2D/controls/line.ts
  43. 19 21
      gui/src/2D/controls/multiLine.ts
  44. 31 35
      gui/src/2D/controls/radioButton.ts
  45. 0 435
      gui/src/2D/controls/scrollViewer.ts
  46. 375 0
      gui/src/2D/controls/scrollViewers/scrollViewer.ts
  47. 74 0
      gui/src/2D/controls/scrollViewers/scrollViewerWindow.ts
  48. 1 1
      gui/src/2D/controls/selector.ts
  49. 0 227
      gui/src/2D/controls/slider.ts
  50. 4 3
      gui/src/2D/controls/baseSlider.ts
  51. 46 44
      gui/src/2D/controls/imageBasedSlider.ts
  52. 155 0
      gui/src/2D/controls/sliders/scrollBar.ts
  53. 240 0
      gui/src/2D/controls/sliders/slider.ts
  54. 41 22
      gui/src/2D/controls/stackPanel.ts
  55. 49 25
      gui/src/2D/controls/textBlock.ts
  56. 13 0
      gui/src/2D/valueAndUnit.ts
  57. 9 1
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  58. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx
  59. 42 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/scrollViewerPropertyGridComponent.tsx
  60. 1 0
      inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx
  61. 5 0
      inspector/src/components/sceneExplorer/entities/gui/advancedDynamicTextureTreeItemComponent.tsx
  62. 22 0
      src/Cameras/arcRotateCamera.ts
  63. 20 0
      src/Maths/math.ts

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 7127 - 4227
Playground/babylon.d.txt


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 4047 - 4022
dist/preview release/babylon.d.ts


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/babylon.js


+ 53 - 2
dist/preview release/babylon.max.js

@@ -5688,6 +5688,16 @@ var BABYLON;
         Vector3.Up = function () {
             return new Vector3(0.0, 1.0, 0.0);
         };
+        Object.defineProperty(Vector3, "UpReadOnly", {
+            /**
+             * Gets a up Vector3 that must not be updated
+             */
+            get: function () {
+                return Vector3._UpReadOnly;
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Returns a new Vector3 set to (0.0, -1.0, 0.0)
          * @returns a new down Vector3
@@ -6165,6 +6175,7 @@ var BABYLON;
             Quaternion.RotationQuaternionFromAxisToRef(axis1, axis2, axis3, quat);
             quat.toEulerAnglesToRef(ref);
         };
+        Vector3._UpReadOnly = Vector3.Up();
         return Vector3;
     }());
     BABYLON.Vector3 = Vector3;
@@ -6823,6 +6834,16 @@ var BABYLON;
             result.z = (x * m[2]) + (y * m[6]) + (z * m[10]);
             result.w = w;
         };
+        /**
+         * Creates a new Vector4 from a Vector3
+         * @param source defines the source data
+         * @param w defines the 4th component (default is 0)
+         * @returns a new Vector4
+         */
+        Vector4.FromVector3 = function (source, w) {
+            if (w === void 0) { w = 0; }
+            return new Vector4(source.x, source.y, source.z, w);
+        };
         return Vector4;
     }());
     BABYLON.Vector4 = Vector4;
@@ -52632,6 +52653,19 @@ var BABYLON;
             }
             var target = this._getTargetPosition();
             this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
+            // Rotate according to up vector
+            if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
+                if (!this._tempAxisVector) {
+                    this._tempAxisVector = new BABYLON.Vector3();
+                    this._tempAxisRotationMatrix = new BABYLON.Matrix();
+                }
+                BABYLON.Vector3.CrossToRef(BABYLON.Vector3.Up(), this.upVector, this._tempAxisVector);
+                this._tempAxisVector.normalize();
+                var angle = Math.acos(BABYLON.Vector3.Dot(BABYLON.Vector3.UpReadOnly, this.upVector));
+                BABYLON.Matrix.RotationAxisToRef(this._tempAxisVector, angle, this._tempAxisRotationMatrix);
+                this._tempAxisVector.copyFrom(this._computationVector);
+                BABYLON.Vector3.TransformCoordinatesToRef(this._tempAxisVector, this._tempAxisRotationMatrix, this._computationVector);
+            }
             target.addToRef(this._computationVector, this._newPosition);
             if (this.getScene().collisionsEnabled && this.checkCollisions) {
                 if (!this._collider) {
@@ -99909,6 +99943,7 @@ var BABYLON;
              */
             this.name = "AmmoJSPlugin";
             this._timeStep = 1 / 60;
+            this._fixedTimeStep = 1 / 60;
             this._maxSteps = 5;
             this._tmpQuaternion = new BABYLON.Quaternion();
             this._tmpContactCallbackResult = false;
@@ -99952,6 +99987,20 @@ var BABYLON;
             this._timeStep = timeStep;
         };
         /**
+         * Increment to step forward in the physics engine (If timeStep is set to 1/60 and fixedTimeStep is set to 1/120 the physics engine should run 2 steps per frame) (Default: 1/60)
+         * @param fixedTimeStep fixedTimeStep to use in seconds
+         */
+        AmmoJSPlugin.prototype.setFixedTimeStep = function (fixedTimeStep) {
+            this._fixedTimeStep = fixedTimeStep;
+        };
+        /**
+         * Sets the maximum number of steps by the physics engine per frame (Default: 5)
+         * @param maxSteps the maximum number of steps by the physics engine per frame
+         */
+        AmmoJSPlugin.prototype.setMaxSteps = function (maxSteps) {
+            this._maxSteps = maxSteps;
+        };
+        /**
          * Gets the current timestep (only used if useDeltaForWorldStep is false in the constructor)
          * @returns the current timestep in seconds
          */
@@ -100012,7 +100061,7 @@ var BABYLON;
                 // Update physics world objects to match babylon world
                 impostor.beforeStep();
             }
-            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps);
+            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps, this._fixedTimeStep);
             for (var _a = 0, impostors_2 = impostors; _a < impostors_2.length; _a++) {
                 var mainImpostor = impostors_2[_a];
                 // After physics update make babylon world objects match physics world objects
@@ -100156,7 +100205,9 @@ var BABYLON;
          * @param impostorJoint the imposter joint to remove the joint from
          */
         AmmoJSPlugin.prototype.removeJoint = function (impostorJoint) {
-            this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+            if (this.world) {
+                this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+            }
         };
         // adds all verticies (including child verticies) to the triangle mesh
         AmmoJSPlugin.prototype._addMeshVerts = function (btTriangleMesh, topLevelObject, object) {

+ 53 - 2
dist/preview release/babylon.no-module.max.js

@@ -5655,6 +5655,16 @@ var BABYLON;
         Vector3.Up = function () {
             return new Vector3(0.0, 1.0, 0.0);
         };
+        Object.defineProperty(Vector3, "UpReadOnly", {
+            /**
+             * Gets a up Vector3 that must not be updated
+             */
+            get: function () {
+                return Vector3._UpReadOnly;
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Returns a new Vector3 set to (0.0, -1.0, 0.0)
          * @returns a new down Vector3
@@ -6132,6 +6142,7 @@ var BABYLON;
             Quaternion.RotationQuaternionFromAxisToRef(axis1, axis2, axis3, quat);
             quat.toEulerAnglesToRef(ref);
         };
+        Vector3._UpReadOnly = Vector3.Up();
         return Vector3;
     }());
     BABYLON.Vector3 = Vector3;
@@ -6790,6 +6801,16 @@ var BABYLON;
             result.z = (x * m[2]) + (y * m[6]) + (z * m[10]);
             result.w = w;
         };
+        /**
+         * Creates a new Vector4 from a Vector3
+         * @param source defines the source data
+         * @param w defines the 4th component (default is 0)
+         * @returns a new Vector4
+         */
+        Vector4.FromVector3 = function (source, w) {
+            if (w === void 0) { w = 0; }
+            return new Vector4(source.x, source.y, source.z, w);
+        };
         return Vector4;
     }());
     BABYLON.Vector4 = Vector4;
@@ -52599,6 +52620,19 @@ var BABYLON;
             }
             var target = this._getTargetPosition();
             this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
+            // Rotate according to up vector
+            if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
+                if (!this._tempAxisVector) {
+                    this._tempAxisVector = new BABYLON.Vector3();
+                    this._tempAxisRotationMatrix = new BABYLON.Matrix();
+                }
+                BABYLON.Vector3.CrossToRef(BABYLON.Vector3.Up(), this.upVector, this._tempAxisVector);
+                this._tempAxisVector.normalize();
+                var angle = Math.acos(BABYLON.Vector3.Dot(BABYLON.Vector3.UpReadOnly, this.upVector));
+                BABYLON.Matrix.RotationAxisToRef(this._tempAxisVector, angle, this._tempAxisRotationMatrix);
+                this._tempAxisVector.copyFrom(this._computationVector);
+                BABYLON.Vector3.TransformCoordinatesToRef(this._tempAxisVector, this._tempAxisRotationMatrix, this._computationVector);
+            }
             target.addToRef(this._computationVector, this._newPosition);
             if (this.getScene().collisionsEnabled && this.checkCollisions) {
                 if (!this._collider) {
@@ -99876,6 +99910,7 @@ var BABYLON;
              */
             this.name = "AmmoJSPlugin";
             this._timeStep = 1 / 60;
+            this._fixedTimeStep = 1 / 60;
             this._maxSteps = 5;
             this._tmpQuaternion = new BABYLON.Quaternion();
             this._tmpContactCallbackResult = false;
@@ -99919,6 +99954,20 @@ var BABYLON;
             this._timeStep = timeStep;
         };
         /**
+         * Increment to step forward in the physics engine (If timeStep is set to 1/60 and fixedTimeStep is set to 1/120 the physics engine should run 2 steps per frame) (Default: 1/60)
+         * @param fixedTimeStep fixedTimeStep to use in seconds
+         */
+        AmmoJSPlugin.prototype.setFixedTimeStep = function (fixedTimeStep) {
+            this._fixedTimeStep = fixedTimeStep;
+        };
+        /**
+         * Sets the maximum number of steps by the physics engine per frame (Default: 5)
+         * @param maxSteps the maximum number of steps by the physics engine per frame
+         */
+        AmmoJSPlugin.prototype.setMaxSteps = function (maxSteps) {
+            this._maxSteps = maxSteps;
+        };
+        /**
          * Gets the current timestep (only used if useDeltaForWorldStep is false in the constructor)
          * @returns the current timestep in seconds
          */
@@ -99979,7 +100028,7 @@ var BABYLON;
                 // Update physics world objects to match babylon world
                 impostor.beforeStep();
             }
-            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps);
+            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps, this._fixedTimeStep);
             for (var _a = 0, impostors_2 = impostors; _a < impostors_2.length; _a++) {
                 var mainImpostor = impostors_2[_a];
                 // After physics update make babylon world objects match physics world objects
@@ -100123,7 +100172,9 @@ var BABYLON;
          * @param impostorJoint the imposter joint to remove the joint from
          */
         AmmoJSPlugin.prototype.removeJoint = function (impostorJoint) {
-            this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+            if (this.world) {
+                this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+            }
         };
         // adds all verticies (including child verticies) to the triangle mesh
         AmmoJSPlugin.prototype._addMeshVerts = function (btTriangleMesh, topLevelObject, object) {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/babylon.worker.js


+ 53 - 2
dist/preview release/es6.js

@@ -5655,6 +5655,16 @@ var BABYLON;
         Vector3.Up = function () {
             return new Vector3(0.0, 1.0, 0.0);
         };
+        Object.defineProperty(Vector3, "UpReadOnly", {
+            /**
+             * Gets a up Vector3 that must not be updated
+             */
+            get: function () {
+                return Vector3._UpReadOnly;
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Returns a new Vector3 set to (0.0, -1.0, 0.0)
          * @returns a new down Vector3
@@ -6132,6 +6142,7 @@ var BABYLON;
             Quaternion.RotationQuaternionFromAxisToRef(axis1, axis2, axis3, quat);
             quat.toEulerAnglesToRef(ref);
         };
+        Vector3._UpReadOnly = Vector3.Up();
         return Vector3;
     }());
     BABYLON.Vector3 = Vector3;
@@ -6790,6 +6801,16 @@ var BABYLON;
             result.z = (x * m[2]) + (y * m[6]) + (z * m[10]);
             result.w = w;
         };
+        /**
+         * Creates a new Vector4 from a Vector3
+         * @param source defines the source data
+         * @param w defines the 4th component (default is 0)
+         * @returns a new Vector4
+         */
+        Vector4.FromVector3 = function (source, w) {
+            if (w === void 0) { w = 0; }
+            return new Vector4(source.x, source.y, source.z, w);
+        };
         return Vector4;
     }());
     BABYLON.Vector4 = Vector4;
@@ -52599,6 +52620,19 @@ var BABYLON;
             }
             var target = this._getTargetPosition();
             this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
+            // Rotate according to up vector
+            if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
+                if (!this._tempAxisVector) {
+                    this._tempAxisVector = new BABYLON.Vector3();
+                    this._tempAxisRotationMatrix = new BABYLON.Matrix();
+                }
+                BABYLON.Vector3.CrossToRef(BABYLON.Vector3.Up(), this.upVector, this._tempAxisVector);
+                this._tempAxisVector.normalize();
+                var angle = Math.acos(BABYLON.Vector3.Dot(BABYLON.Vector3.UpReadOnly, this.upVector));
+                BABYLON.Matrix.RotationAxisToRef(this._tempAxisVector, angle, this._tempAxisRotationMatrix);
+                this._tempAxisVector.copyFrom(this._computationVector);
+                BABYLON.Vector3.TransformCoordinatesToRef(this._tempAxisVector, this._tempAxisRotationMatrix, this._computationVector);
+            }
             target.addToRef(this._computationVector, this._newPosition);
             if (this.getScene().collisionsEnabled && this.checkCollisions) {
                 if (!this._collider) {
@@ -99876,6 +99910,7 @@ var BABYLON;
              */
             this.name = "AmmoJSPlugin";
             this._timeStep = 1 / 60;
+            this._fixedTimeStep = 1 / 60;
             this._maxSteps = 5;
             this._tmpQuaternion = new BABYLON.Quaternion();
             this._tmpContactCallbackResult = false;
@@ -99919,6 +99954,20 @@ var BABYLON;
             this._timeStep = timeStep;
         };
         /**
+         * Increment to step forward in the physics engine (If timeStep is set to 1/60 and fixedTimeStep is set to 1/120 the physics engine should run 2 steps per frame) (Default: 1/60)
+         * @param fixedTimeStep fixedTimeStep to use in seconds
+         */
+        AmmoJSPlugin.prototype.setFixedTimeStep = function (fixedTimeStep) {
+            this._fixedTimeStep = fixedTimeStep;
+        };
+        /**
+         * Sets the maximum number of steps by the physics engine per frame (Default: 5)
+         * @param maxSteps the maximum number of steps by the physics engine per frame
+         */
+        AmmoJSPlugin.prototype.setMaxSteps = function (maxSteps) {
+            this._maxSteps = maxSteps;
+        };
+        /**
          * Gets the current timestep (only used if useDeltaForWorldStep is false in the constructor)
          * @returns the current timestep in seconds
          */
@@ -99979,7 +100028,7 @@ var BABYLON;
                 // Update physics world objects to match babylon world
                 impostor.beforeStep();
             }
-            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps);
+            this._stepSimulation(this._useDeltaForWorldStep ? delta : this._timeStep, this._maxSteps, this._fixedTimeStep);
             for (var _a = 0, impostors_2 = impostors; _a < impostors_2.length; _a++) {
                 var mainImpostor = impostors_2[_a];
                 // After physics update make babylon world objects match physics world objects
@@ -100123,7 +100172,9 @@ var BABYLON;
          * @param impostorJoint the imposter joint to remove the joint from
          */
         AmmoJSPlugin.prototype.removeJoint = function (impostorJoint) {
-            this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+            if (this.world) {
+                this.world.removeConstraint(impostorJoint.joint.physicsJoint);
+            }
         };
         // adds all verticies (including child verticies) to the triangle mesh
         AmmoJSPlugin.prototype._addMeshVerts = function (btTriangleMesh, topLevelObject, object) {

+ 100 - 75
dist/preview release/gui/babylon.gui.d.ts

@@ -1,6 +1,7 @@
 /*Babylon.js GUI*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
+//   ../../../../Tools/gulp/2D
 declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {
@@ -60,8 +61,6 @@ declare module BABYLON.GUI {
             _layerToDispose: BABYLON.Nullable<BABYLON.Layer>;
             /** @hidden */
             _linkedControls: Control[];
-            /** @hidden */
-            _needRedraw: boolean;
             /**
                 * BABYLON.Observable event triggered each time an clipboard event is received from the rendering canvas
                 */
@@ -516,6 +515,13 @@ declare module BABYLON.GUI {
                 */
             getValueInPixel(host: AdvancedDynamicTexture, refValue: number): number;
             /**
+                * Update the current value and unit. This should be done cautiously as the GUi won't be marked as dirty with this function.
+                * @param value defines the value to store
+                * @param unit defines the unit to store
+                * @returns the current ValueAndUnit
+                */
+            updateInPlace(value: number, unit?: number): ValueAndUnit;
+            /**
                 * Gets the value accordingly to its unit
                 * @param host  defines the root host
                 * @returns the value
@@ -724,7 +730,7 @@ declare module BABYLON.GUI {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
@@ -759,7 +765,7 @@ declare module BABYLON.GUI {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerMove(target: Control, coordinates: BABYLON.Vector2): void;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
@@ -782,6 +788,8 @@ declare module BABYLON.GUI {
             protected _adaptWidthToChildren: boolean;
             /** @hidden */
             protected _adaptHeightToChildren: boolean;
+            /** @hidden */
+            protected _rebuildLayout: boolean;
             /** Gets or sets a boolean indicating if the container should try to adapt to its children height */
             adaptHeightToChildren: boolean;
             /** Gets or sets a boolean indicating if the container should try to adapt to its children width */
@@ -836,20 +844,27 @@ declare module BABYLON.GUI {
             /** @hidden */
             _reOrderControl(control: Control): void;
             /** @hidden */
+            _offsetLeft(offset: number): void;
+            /** @hidden */
+            _offsetTop(offset: number): void;
+            /** @hidden */
             _markAllAsDirty(): void;
             /** @hidden */
             protected _localDraw(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _link(root: BABYLON.Nullable<Container>, host: AdvancedDynamicTexture): void;
+            _link(host: AdvancedDynamicTexture): void;
+            /** @hidden */
+            protected _beforeLayout(): void;
+            /** @hidden */
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            protected _postMeasure(): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _getDescendants(results: Control[], directDescendantsOnly?: boolean, predicate?: (control: Control) => boolean): void;
             /** @hidden */
             _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean;
             /** @hidden */
-            protected _clipForChildren(context: CanvasRenderingContext2D): void;
-            /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
@@ -868,8 +883,6 @@ declare module BABYLON.GUI {
                 */
             static AllowAlphaInheritance: boolean;
             /** @hidden */
-            _root: BABYLON.Nullable<Container>;
-            /** @hidden */
             _host: AdvancedDynamicTexture;
             /** Gets or sets the control parent */
             parent: BABYLON.Nullable<Container>;
@@ -890,6 +903,8 @@ declare module BABYLON.GUI {
             /** @hidden */
             protected _verticalAlignment: number;
             /** @hidden */
+            protected _isDirty: boolean;
+            /** @hidden */
             _tempParentMeasure: Measure;
             /** @hidden */
             protected _cachedParentMeasure: Measure;
@@ -906,6 +921,8 @@ declare module BABYLON.GUI {
             protected _isEnabled: boolean;
             protected _disabledColor: string;
             /** @hidden */
+            _isClipped: boolean;
+            /** @hidden */
             _tag: any;
             /**
                 * Gets or sets the unique id of the node. Please note that this number will be updated when the control is added to a container
@@ -1170,6 +1187,12 @@ declare module BABYLON.GUI {
             name?: string | undefined);
             /** @hidden */
             protected _getTypeName(): string;
+            /**
+                * Gets the first ascendant in the hierarchy of the given type
+                * @param className defines the required type
+                * @returns the ascendant or null if not found
+                */
+            getAscendantOfClass(className: string): BABYLON.Nullable<Control>;
             /** @hidden */
             _resetFontCache(): void;
             /**
@@ -1221,6 +1244,10 @@ declare module BABYLON.GUI {
             /** @hidden */
             _moveToProjectedPosition(projectedPosition: BABYLON.Vector3): void;
             /** @hidden */
+            _offsetLeft(offset: number): void;
+            /** @hidden */
+            _offsetTop(offset: number): void;
+            /** @hidden */
             _markMatrixAsDirty(): void;
             /** @hidden */
             _flagDescendantsAsMatrixDirty(): void;
@@ -1229,19 +1256,19 @@ declare module BABYLON.GUI {
             /** @hidden */
             _markAllAsDirty(): void;
             /** @hidden */
-            _link(root: BABYLON.Nullable<Container>, host: AdvancedDynamicTexture): void;
+            _link(host: AdvancedDynamicTexture): void;
             /** @hidden */
             protected _transform(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _renderHighlight(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** @hidden */
             protected _applyStates(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
             /** @hidden */
-            protected _clip(context: CanvasRenderingContext2D): void;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
             _measure(): void;
             /** @hidden */
@@ -1251,7 +1278,11 @@ declare module BABYLON.GUI {
             /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _clipForChildren(context: CanvasRenderingContext2D): void;
+            /** @hidden */
+            _render(context: CanvasRenderingContext2D): boolean;
+            /** @hidden */
+            _draw(context: CanvasRenderingContext2D): void;
             /**
                 * Tests if a given coordinates belong to the current control
                 * @param x defines x coordinate to test
@@ -1347,6 +1378,18 @@ declare module BABYLON.GUI {
             /** Gets the list of children */
             readonly children: Control[];
             /**
+                * Gets the definition of a specific row
+                * @param index defines the index of the row
+                * @returns the row definition
+                */
+            getRowDefinition(index: number): BABYLON.Nullable<ValueAndUnit>;
+            /**
+                * Gets the definition of a specific column
+                * @param index defines the index of the column
+                * @returns the column definition
+                */
+            getColumnDefinition(index: number): BABYLON.Nullable<ValueAndUnit>;
+            /**
                 * Adds a new row to the grid
                 * @param height defines the height of the row (either in pixel or a value between 0 and 1)
                 * @param isPixel defines if the height is expressed in pixel (or in percentage)
@@ -1418,7 +1461,7 @@ declare module BABYLON.GUI {
             protected _getGridDefinitions(definitionCallback: (lefts: number[], tops: number[], widths: number[], heights: number[]) => void): void;
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             _flagDescendantsAsMatrixDirty(): void;
-            protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
     }
@@ -1492,7 +1535,8 @@ declare module BABYLON.GUI {
             protected _getTypeName(): string;
             /** Force the control to synchronize with its content */
             synchronizeSizeWithContent(): void;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             dispose(): void;
             /** STRETCH_NONE */
             static readonly STRETCH_NONE: number;
@@ -1591,7 +1635,7 @@ declare module BABYLON.GUI {
                 * @param evt Defines the KeyboardEvent
                 */
             processKeyboard(evt: KeyboardEvent): void;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
             protected _beforeRenderText(text: string): string;
@@ -1634,7 +1678,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _measure(): void;
             protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /**
@@ -1709,7 +1753,7 @@ declare module BABYLON.GUI {
             horizontalAlignment: number;
             verticalAlignment: number;
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             _measure(): void;
             protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
@@ -1740,7 +1784,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
                 * Utility function to easily create a radio button with a header
@@ -1777,7 +1821,10 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
+            /** @hidden */
             protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _postMeasure(): void;
     }
 }
 declare module BABYLON.GUI {
@@ -1959,60 +2006,31 @@ declare module BABYLON.GUI {
         * Class used to hold a viewer window and sliders in a grid
      */
     export class ScrollViewer extends Rectangle {
-            /** name of ScrollViewer */
-            name?: string | undefined;
-            /**
-                * Adds windowContents to the grid view window
-                * @param windowContents the contents to add the grid view window
-                */
-            addToWindow(windowContents: Control): void;
-            /**
-                * Gets or sets a value indicating the padding to use on the left of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingLeft: string | number;
             /**
-                * Gets a value indicating the padding in pixels to use on the left of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingLeftInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the right of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingRight: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the right of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingRightInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the top of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingTop: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the top of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingTopInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the bottom of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                * Adds a new control to the current container
+                * @param control defines the control to add
+                * @returns the current container
                 */
-            paddingBottom: string | number;
+            addControl(control: BABYLON.Nullable<Control>): Container;
             /**
-                * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                * Removes a control from the current container
+                * @param control defines the control to remove
+                * @returns the current container
                 */
-            readonly paddingBottomInPixels: number;
+            removeControl(control: Control): Container;
+            /** Gets the list of children */
+            readonly children: Control[];
+            _flagDescendantsAsMatrixDirty(): void;
             /**
              * Creates a new ScrollViewer
              * @param name of ScrollViewer
              */
-            constructor(
-            /** name of ScrollViewer */
-            name?: string | undefined);
+            constructor(name?: string);
+            /** Reset the scroll viewer window to initial size */
+            resetWindow(): void;
+            protected _getTypeName(): string;
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _postMeasure(): void;
             /**
                 * Gets or sets the mouse wheel precision
                 * from 0 to 1 with a default value of 0.05
@@ -2020,12 +2038,14 @@ declare module BABYLON.GUI {
             wheelPrecision: number;
             /** Gets or sets the bar color */
             barColor: string;
+            /** Gets or sets the size of the bar */
+            barSize: number;
             /** Gets or sets the bar color */
             barBorderColor: string;
             /** Gets or sets the bar background */
             barBackground: string;
-            /** @hidden */
-            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _link(host: AdvancedDynamicTexture): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
     }
@@ -2111,10 +2131,10 @@ declare module BABYLON.GUI {
                 */
             name?: string | undefined, text?: string);
             protected _getTypeName(): string;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _applyStates(context: CanvasRenderingContext2D): void;
-            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[];
             protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
             protected _parseLineEllipsis(line: string | undefined, width: number, context: CanvasRenderingContext2D): object;
@@ -2265,7 +2285,7 @@ declare module BABYLON.GUI {
                 * @param name defines the control name
                 */
             constructor(name?: string | undefined);
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _getTypeName(): string;
     }
 }
@@ -2317,6 +2337,8 @@ declare module BABYLON.GUI {
             protected _getThumbPosition(): number;
             protected _getThumbThickness(type: string): number;
             protected _prepareRenderingData(type: string): void;
+            /** @hidden */
+            protected _updateValueFromPointer(x: number, y: number): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerMove(target: Control, coordinates: BABYLON.Vector2): void;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
@@ -2328,6 +2350,9 @@ declare module BABYLON.GUI {
         */
     export class Slider extends BaseSlider {
             name?: string | undefined;
+            protected _displayValueBar: boolean;
+            /** Gets or sets a boolean indicating if the value bar must be rendered */
+            displayValueBar: boolean;
             /** Gets or sets border color */
             borderColor: string;
             /** Gets or sets background color */
@@ -2340,7 +2365,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 declare module BABYLON.GUI {
@@ -2368,7 +2393,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 declare module BABYLON.GUI {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/gui/babylon.gui.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js.map


+ 215 - 169
dist/preview release/gui/babylon.gui.module.d.ts

@@ -1,6 +1,7 @@
 /*Babylon.js GUI*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
+//   ../../../../Tools/gulp/2D
 
 declare module 'babylonjs-gui' {
     export * from "babylonjs-gui/2D";
@@ -40,14 +41,14 @@ declare module 'babylonjs-gui/2D/controls' {
     export * from "babylonjs-gui/2D/controls/radioButton";
     export * from "babylonjs-gui/2D/controls/stackPanel";
     export * from "babylonjs-gui/2D/controls/selector";
-    export * from "babylonjs-gui/2D/controls/scrollViewer";
+    export * from "babylonjs-gui/2D/controls/scrollViewers/scrollViewer";
     export * from "babylonjs-gui/2D/controls/textBlock";
     export * from "babylonjs-gui/2D/controls/virtualKeyboard";
     export * from "babylonjs-gui/2D/controls/rectangle";
     export * from "babylonjs-gui/2D/controls/displayGrid";
-    export * from "babylonjs-gui/2D/controls/baseSlider";
-    export * from "babylonjs-gui/2D/controls/slider";
-    export * from "babylonjs-gui/2D/controls/imageBasedSlider";
+    export * from "babylonjs-gui/2D/controls/sliders/baseSlider";
+    export * from "babylonjs-gui/2D/controls/sliders/slider";
+    export * from "babylonjs-gui/2D/controls/sliders/imageBasedSlider";
     export * from "babylonjs-gui/2D/controls/statics";
 }
 
@@ -106,8 +107,6 @@ declare module 'babylonjs-gui/2D/advancedDynamicTexture' {
             _layerToDispose: Nullable<Layer>;
             /** @hidden */
             _linkedControls: Control[];
-            /** @hidden */
-            _needRedraw: boolean;
             /**
                 * Observable event triggered each time an clipboard event is received from the rendering canvas
                 */
@@ -575,6 +574,13 @@ declare module 'babylonjs-gui/2D/valueAndUnit' {
                 */
             getValueInPixel(host: AdvancedDynamicTexture, refValue: number): number;
             /**
+                * Update the current value and unit. This should be done cautiously as the GUi won't be marked as dirty with this function.
+                * @param value defines the value to store
+                * @param unit defines the unit to store
+                * @returns the current ValueAndUnit
+                */
+            updateInPlace(value: number, unit?: number): ValueAndUnit;
+            /**
                 * Gets the value accordingly to its unit
                 * @param host  defines the root host
                 * @returns the value
@@ -788,7 +794,6 @@ declare module 'babylonjs-gui/2D/controls/button' {
 
 declare module 'babylonjs-gui/2D/controls/checkbox' {
     import { Control } from "babylonjs-gui/2D/controls/control";
-    import { Measure } from "babylonjs-gui/2D/measure";
     import { Observable, Vector2 } from "babylonjs";
     import { StackPanel } from "babylonjs-gui/2D/controls/stackPanel";
     /**
@@ -815,7 +820,7 @@ declare module 'babylonjs-gui/2D/controls/checkbox' {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
@@ -831,7 +836,6 @@ declare module 'babylonjs-gui/2D/controls/checkbox' {
 declare module 'babylonjs-gui/2D/controls/colorpicker' {
     import { Control } from "babylonjs-gui/2D/controls/control";
     import { Color3, Observable, Vector2 } from "babylonjs";
-    import { Measure } from "babylonjs-gui/2D/measure";
     /** Class used to create color pickers */
     export class ColorPicker extends Control {
             name?: string | undefined;
@@ -854,7 +858,7 @@ declare module 'babylonjs-gui/2D/controls/colorpicker' {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerMove(target: Control, coordinates: Vector2): void;
             _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
@@ -882,6 +886,8 @@ declare module 'babylonjs-gui/2D/controls/container' {
             protected _adaptWidthToChildren: boolean;
             /** @hidden */
             protected _adaptHeightToChildren: boolean;
+            /** @hidden */
+            protected _rebuildLayout: boolean;
             /** Gets or sets a boolean indicating if the container should try to adapt to its children height */
             adaptHeightToChildren: boolean;
             /** Gets or sets a boolean indicating if the container should try to adapt to its children width */
@@ -936,20 +942,27 @@ declare module 'babylonjs-gui/2D/controls/container' {
             /** @hidden */
             _reOrderControl(control: Control): void;
             /** @hidden */
+            _offsetLeft(offset: number): void;
+            /** @hidden */
+            _offsetTop(offset: number): void;
+            /** @hidden */
             _markAllAsDirty(): void;
             /** @hidden */
             protected _localDraw(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _link(root: Nullable<Container>, host: AdvancedDynamicTexture): void;
+            _link(host: AdvancedDynamicTexture): void;
+            /** @hidden */
+            protected _beforeLayout(): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            protected _postMeasure(): void;
+            /** @hidden */
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _getDescendants(results: Control[], directDescendantsOnly?: boolean, predicate?: (control: Control) => boolean): void;
             /** @hidden */
             _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean;
             /** @hidden */
-            protected _clipForChildren(context: CanvasRenderingContext2D): void;
-            /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
@@ -976,8 +989,6 @@ declare module 'babylonjs-gui/2D/controls/control' {
                 */
             static AllowAlphaInheritance: boolean;
             /** @hidden */
-            _root: Nullable<Container>;
-            /** @hidden */
             _host: AdvancedDynamicTexture;
             /** Gets or sets the control parent */
             parent: Nullable<Container>;
@@ -998,6 +1009,8 @@ declare module 'babylonjs-gui/2D/controls/control' {
             /** @hidden */
             protected _verticalAlignment: number;
             /** @hidden */
+            protected _isDirty: boolean;
+            /** @hidden */
             _tempParentMeasure: Measure;
             /** @hidden */
             protected _cachedParentMeasure: Measure;
@@ -1014,6 +1027,8 @@ declare module 'babylonjs-gui/2D/controls/control' {
             protected _isEnabled: boolean;
             protected _disabledColor: string;
             /** @hidden */
+            _isClipped: boolean;
+            /** @hidden */
             _tag: any;
             /**
                 * Gets or sets the unique id of the node. Please note that this number will be updated when the control is added to a container
@@ -1278,6 +1293,12 @@ declare module 'babylonjs-gui/2D/controls/control' {
             name?: string | undefined);
             /** @hidden */
             protected _getTypeName(): string;
+            /**
+                * Gets the first ascendant in the hierarchy of the given type
+                * @param className defines the required type
+                * @returns the ascendant or null if not found
+                */
+            getAscendantOfClass(className: string): Nullable<Control>;
             /** @hidden */
             _resetFontCache(): void;
             /**
@@ -1329,6 +1350,10 @@ declare module 'babylonjs-gui/2D/controls/control' {
             /** @hidden */
             _moveToProjectedPosition(projectedPosition: Vector3): void;
             /** @hidden */
+            _offsetLeft(offset: number): void;
+            /** @hidden */
+            _offsetTop(offset: number): void;
+            /** @hidden */
             _markMatrixAsDirty(): void;
             /** @hidden */
             _flagDescendantsAsMatrixDirty(): void;
@@ -1337,19 +1362,19 @@ declare module 'babylonjs-gui/2D/controls/control' {
             /** @hidden */
             _markAllAsDirty(): void;
             /** @hidden */
-            _link(root: Nullable<Container>, host: AdvancedDynamicTexture): void;
+            _link(host: AdvancedDynamicTexture): void;
             /** @hidden */
             protected _transform(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _renderHighlight(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** @hidden */
             protected _applyStates(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
             /** @hidden */
-            protected _clip(context: CanvasRenderingContext2D): void;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
             _measure(): void;
             /** @hidden */
@@ -1359,7 +1384,11 @@ declare module 'babylonjs-gui/2D/controls/control' {
             /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _clipForChildren(context: CanvasRenderingContext2D): void;
+            /** @hidden */
+            _render(context: CanvasRenderingContext2D): boolean;
+            /** @hidden */
+            _draw(context: CanvasRenderingContext2D): void;
             /**
                 * Tests if a given coordinates belong to the current control
                 * @param x defines x coordinate to test
@@ -1444,6 +1473,7 @@ declare module 'babylonjs-gui/2D/controls/ellipse' {
 
 declare module 'babylonjs-gui/2D/controls/grid' {
     import { Container } from "babylonjs-gui/2D/controls/container";
+    import { ValueAndUnit } from "babylonjs-gui/2D/valueAndUnit";
     import { Control } from "babylonjs-gui/2D/controls/control";
     import { Measure } from "babylonjs-gui/2D/measure";
     import { Nullable } from "babylonjs";
@@ -1463,6 +1493,18 @@ declare module 'babylonjs-gui/2D/controls/grid' {
             /** Gets the list of children */
             readonly children: Control[];
             /**
+                * Gets the definition of a specific row
+                * @param index defines the index of the row
+                * @returns the row definition
+                */
+            getRowDefinition(index: number): Nullable<ValueAndUnit>;
+            /**
+                * Gets the definition of a specific column
+                * @param index defines the index of the column
+                * @returns the column definition
+                */
+            getColumnDefinition(index: number): Nullable<ValueAndUnit>;
+            /**
                 * Adds a new row to the grid
                 * @param height defines the height of the row (either in pixel or a value between 0 and 1)
                 * @param isPixel defines if the height is expressed in pixel (or in percentage)
@@ -1534,7 +1576,7 @@ declare module 'babylonjs-gui/2D/controls/grid' {
             protected _getGridDefinitions(definitionCallback: (lefts: number[], tops: number[], widths: number[], heights: number[]) => void): void;
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             _flagDescendantsAsMatrixDirty(): void;
-            protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
     }
@@ -1543,7 +1585,7 @@ declare module 'babylonjs-gui/2D/controls/grid' {
 declare module 'babylonjs-gui/2D/controls/image' {
     import { Control } from "babylonjs-gui/2D/controls/control";
     import { Nullable, Observable } from "babylonjs";
-    import { Measure } from "babylonjs-gui/2D/measure";
+    import { Measure } from "2D";
     /**
         * Class used to create 2D images
         */
@@ -1612,7 +1654,8 @@ declare module 'babylonjs-gui/2D/controls/image' {
             protected _getTypeName(): string;
             /** Force the control to synchronize with its content */
             synchronizeSizeWithContent(): void;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             dispose(): void;
             /** STRETCH_NONE */
             static readonly STRETCH_NONE: number;
@@ -1629,7 +1672,6 @@ declare module 'babylonjs-gui/2D/controls/inputText' {
     import { Control } from "babylonjs-gui/2D/controls/control";
     import { IFocusableControl } from "babylonjs-gui/2D/advancedDynamicTexture";
     import { Nullable, Observable, Vector2 } from 'babylonjs';
-    import { Measure } from "babylonjs-gui/2D/measure";
     import { VirtualKeyboard } from "babylonjs-gui/2D/controls/virtualKeyboard";
     /**
         * Class used to create input text control
@@ -1717,7 +1759,7 @@ declare module 'babylonjs-gui/2D/controls/inputText' {
                 * @param evt Defines the KeyboardEvent
                 */
             processKeyboard(evt: KeyboardEvent): void;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
             protected _beforeRenderText(text: string): string;
@@ -1766,7 +1808,7 @@ declare module 'babylonjs-gui/2D/controls/line' {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _measure(): void;
             protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /**
@@ -1846,7 +1888,7 @@ declare module 'babylonjs-gui/2D/controls/multiLine' {
             horizontalAlignment: number;
             verticalAlignment: number;
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             _measure(): void;
             protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
@@ -1857,7 +1899,6 @@ declare module 'babylonjs-gui/2D/controls/multiLine' {
 declare module 'babylonjs-gui/2D/controls/radioButton' {
     import { Control } from "babylonjs-gui/2D/controls/control";
     import { Observable, Vector2 } from "babylonjs";
-    import { Measure } from "babylonjs-gui/2D/measure";
     import { StackPanel } from "babylonjs-gui/2D/controls";
     /**
         * Class used to create radio button controls
@@ -1882,7 +1923,7 @@ declare module 'babylonjs-gui/2D/controls/radioButton' {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
                 * Utility function to easily create a radio button with a header
@@ -1922,7 +1963,10 @@ declare module 'babylonjs-gui/2D/controls/stackPanel' {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
+            /** @hidden */
             protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _postMeasure(): void;
     }
 }
 
@@ -2103,68 +2147,41 @@ declare module 'babylonjs-gui/2D/controls/selector' {
     }
 }
 
-declare module 'babylonjs-gui/2D/controls/scrollViewer' {
-    import { Measure } from "babylonjs-gui/2D/measure";
+declare module 'babylonjs-gui/2D/controls/scrollViewers/scrollViewer' {
     import { Rectangle } from "babylonjs-gui/2D/controls/rectangle";
     import { Control } from "babylonjs-gui/2D/controls/control";
+    import { Container } from "babylonjs-gui/2D/controls/container";
+    import { Nullable } from "babylonjs";
+    import { AdvancedDynamicTexture, Measure } from "2D";
     /**
         * Class used to hold a viewer window and sliders in a grid
      */
     export class ScrollViewer extends Rectangle {
-            /** name of ScrollViewer */
-            name?: string | undefined;
             /**
-                * Adds windowContents to the grid view window
-                * @param windowContents the contents to add the grid view window
-                */
-            addToWindow(windowContents: Control): void;
-            /**
-                * Gets or sets a value indicating the padding to use on the left of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingLeft: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the left of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingLeftInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the right of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingRight: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the right of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingRightInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the top of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingTop: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the top of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingTopInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the bottom of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                * Adds a new control to the current container
+                * @param control defines the control to add
+                * @returns the current container
                 */
-            paddingBottom: string | number;
+            addControl(control: Nullable<Control>): Container;
             /**
-                * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                * Removes a control from the current container
+                * @param control defines the control to remove
+                * @returns the current container
                 */
-            readonly paddingBottomInPixels: number;
+            removeControl(control: Control): Container;
+            /** Gets the list of children */
+            readonly children: Control[];
+            _flagDescendantsAsMatrixDirty(): void;
             /**
              * Creates a new ScrollViewer
              * @param name of ScrollViewer
              */
-            constructor(
-            /** name of ScrollViewer */
-            name?: string | undefined);
+            constructor(name?: string);
+            /** Reset the scroll viewer window to initial size */
+            resetWindow(): void;
+            protected _getTypeName(): string;
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _postMeasure(): void;
             /**
                 * Gets or sets the mouse wheel precision
                 * from 0 to 1 with a default value of 0.05
@@ -2172,12 +2189,14 @@ declare module 'babylonjs-gui/2D/controls/scrollViewer' {
             wheelPrecision: number;
             /** Gets or sets the bar color */
             barColor: string;
+            /** Gets or sets the size of the bar */
+            barSize: number;
             /** Gets or sets the bar color */
             barBorderColor: string;
             /** Gets or sets the bar background */
             barBackground: string;
-            /** @hidden */
-            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _link(host: AdvancedDynamicTexture): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
     }
@@ -2267,10 +2286,10 @@ declare module 'babylonjs-gui/2D/controls/textBlock' {
                 */
             name?: string | undefined, text?: string);
             protected _getTypeName(): string;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _applyStates(context: CanvasRenderingContext2D): void;
-            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[];
             protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
             protected _parseLineEllipsis(line: string | undefined, width: number, context: CanvasRenderingContext2D): object;
@@ -2402,7 +2421,6 @@ declare module 'babylonjs-gui/2D/controls/rectangle' {
 
 declare module 'babylonjs-gui/2D/controls/displayGrid' {
     import { Control } from "babylonjs-gui/2D/controls";
-    import { Measure } from "babylonjs-gui/2D";
     /** Class used to render a grid  */
     export class DisplayGrid extends Control {
             name?: string | undefined;
@@ -2431,12 +2449,12 @@ declare module 'babylonjs-gui/2D/controls/displayGrid' {
                 * @param name defines the control name
                 */
             constructor(name?: string | undefined);
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _getTypeName(): string;
     }
 }
 
-declare module 'babylonjs-gui/2D/controls/baseSlider' {
+declare module 'babylonjs-gui/2D/controls/sliders/baseSlider' {
     import { Control } from "babylonjs-gui/2D/controls/control";
     import { ValueAndUnit } from "babylonjs-gui/2D/valueAndUnit";
     import { Observable, Vector2 } from "babylonjs";
@@ -2487,20 +2505,24 @@ declare module 'babylonjs-gui/2D/controls/baseSlider' {
             protected _getThumbPosition(): number;
             protected _getThumbThickness(type: string): number;
             protected _prepareRenderingData(type: string): void;
+            /** @hidden */
+            protected _updateValueFromPointer(x: number, y: number): void;
             _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerMove(target: Control, coordinates: Vector2): void;
             _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
     }
 }
 
-declare module 'babylonjs-gui/2D/controls/slider' {
-    import { Measure } from "babylonjs-gui/2D/measure";
-    import { BaseSlider } from "babylonjs-gui/2D/controls/baseSlider";
+declare module 'babylonjs-gui/2D/controls/sliders/slider' {
+    import { BaseSlider } from "babylonjs-gui/2D/controls/sliders/baseSlider";
     /**
         * Class used to create slider controls
         */
     export class Slider extends BaseSlider {
             name?: string | undefined;
+            protected _displayValueBar: boolean;
+            /** Gets or sets a boolean indicating if the value bar must be rendered */
+            displayValueBar: boolean;
             /** Gets or sets border color */
             borderColor: string;
             /** Gets or sets background color */
@@ -2513,13 +2535,12 @@ declare module 'babylonjs-gui/2D/controls/slider' {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 
-declare module 'babylonjs-gui/2D/controls/imageBasedSlider' {
-    import { BaseSlider } from "babylonjs-gui/2D/controls/baseSlider";
-    import { Measure } from "babylonjs-gui/2D/measure";
+declare module 'babylonjs-gui/2D/controls/sliders/imageBasedSlider' {
+    import { BaseSlider } from "babylonjs-gui/2D/controls/sliders/baseSlider";
     import { Image } from "babylonjs-gui/2D/controls/image";
     /**
         * Class used to create slider controls based on images
@@ -2545,7 +2566,7 @@ declare module 'babylonjs-gui/2D/controls/imageBasedSlider' {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 
@@ -3134,7 +3155,8 @@ declare module 'babylonjs-gui/3D/materials/fluentMaterial' {
 
 /*Babylon.js GUI*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
+//   ../../../../Tools/gulp/2D
 declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {
@@ -3194,8 +3216,6 @@ declare module BABYLON.GUI {
             _layerToDispose: BABYLON.Nullable<BABYLON.Layer>;
             /** @hidden */
             _linkedControls: Control[];
-            /** @hidden */
-            _needRedraw: boolean;
             /**
                 * BABYLON.Observable event triggered each time an clipboard event is received from the rendering canvas
                 */
@@ -3650,6 +3670,13 @@ declare module BABYLON.GUI {
                 */
             getValueInPixel(host: AdvancedDynamicTexture, refValue: number): number;
             /**
+                * Update the current value and unit. This should be done cautiously as the GUi won't be marked as dirty with this function.
+                * @param value defines the value to store
+                * @param unit defines the unit to store
+                * @returns the current ValueAndUnit
+                */
+            updateInPlace(value: number, unit?: number): ValueAndUnit;
+            /**
                 * Gets the value accordingly to its unit
                 * @param host  defines the root host
                 * @returns the value
@@ -3858,7 +3885,7 @@ declare module BABYLON.GUI {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
@@ -3893,7 +3920,7 @@ declare module BABYLON.GUI {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerMove(target: Control, coordinates: BABYLON.Vector2): void;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
@@ -3916,6 +3943,8 @@ declare module BABYLON.GUI {
             protected _adaptWidthToChildren: boolean;
             /** @hidden */
             protected _adaptHeightToChildren: boolean;
+            /** @hidden */
+            protected _rebuildLayout: boolean;
             /** Gets or sets a boolean indicating if the container should try to adapt to its children height */
             adaptHeightToChildren: boolean;
             /** Gets or sets a boolean indicating if the container should try to adapt to its children width */
@@ -3970,20 +3999,27 @@ declare module BABYLON.GUI {
             /** @hidden */
             _reOrderControl(control: Control): void;
             /** @hidden */
+            _offsetLeft(offset: number): void;
+            /** @hidden */
+            _offsetTop(offset: number): void;
+            /** @hidden */
             _markAllAsDirty(): void;
             /** @hidden */
             protected _localDraw(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _link(root: BABYLON.Nullable<Container>, host: AdvancedDynamicTexture): void;
+            _link(host: AdvancedDynamicTexture): void;
+            /** @hidden */
+            protected _beforeLayout(): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            protected _postMeasure(): void;
+            /** @hidden */
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _getDescendants(results: Control[], directDescendantsOnly?: boolean, predicate?: (control: Control) => boolean): void;
             /** @hidden */
             _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean;
             /** @hidden */
-            protected _clipForChildren(context: CanvasRenderingContext2D): void;
-            /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
@@ -4002,8 +4038,6 @@ declare module BABYLON.GUI {
                 */
             static AllowAlphaInheritance: boolean;
             /** @hidden */
-            _root: BABYLON.Nullable<Container>;
-            /** @hidden */
             _host: AdvancedDynamicTexture;
             /** Gets or sets the control parent */
             parent: BABYLON.Nullable<Container>;
@@ -4024,6 +4058,8 @@ declare module BABYLON.GUI {
             /** @hidden */
             protected _verticalAlignment: number;
             /** @hidden */
+            protected _isDirty: boolean;
+            /** @hidden */
             _tempParentMeasure: Measure;
             /** @hidden */
             protected _cachedParentMeasure: Measure;
@@ -4040,6 +4076,8 @@ declare module BABYLON.GUI {
             protected _isEnabled: boolean;
             protected _disabledColor: string;
             /** @hidden */
+            _isClipped: boolean;
+            /** @hidden */
             _tag: any;
             /**
                 * Gets or sets the unique id of the node. Please note that this number will be updated when the control is added to a container
@@ -4304,6 +4342,12 @@ declare module BABYLON.GUI {
             name?: string | undefined);
             /** @hidden */
             protected _getTypeName(): string;
+            /**
+                * Gets the first ascendant in the hierarchy of the given type
+                * @param className defines the required type
+                * @returns the ascendant or null if not found
+                */
+            getAscendantOfClass(className: string): BABYLON.Nullable<Control>;
             /** @hidden */
             _resetFontCache(): void;
             /**
@@ -4355,6 +4399,10 @@ declare module BABYLON.GUI {
             /** @hidden */
             _moveToProjectedPosition(projectedPosition: BABYLON.Vector3): void;
             /** @hidden */
+            _offsetLeft(offset: number): void;
+            /** @hidden */
+            _offsetTop(offset: number): void;
+            /** @hidden */
             _markMatrixAsDirty(): void;
             /** @hidden */
             _flagDescendantsAsMatrixDirty(): void;
@@ -4363,19 +4411,19 @@ declare module BABYLON.GUI {
             /** @hidden */
             _markAllAsDirty(): void;
             /** @hidden */
-            _link(root: BABYLON.Nullable<Container>, host: AdvancedDynamicTexture): void;
+            _link(host: AdvancedDynamicTexture): void;
             /** @hidden */
             protected _transform(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _renderHighlight(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** @hidden */
             protected _applyStates(context: CanvasRenderingContext2D): void;
             /** @hidden */
-            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
             /** @hidden */
-            protected _clip(context: CanvasRenderingContext2D): void;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
             _measure(): void;
             /** @hidden */
@@ -4385,7 +4433,11 @@ declare module BABYLON.GUI {
             /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _clipForChildren(context: CanvasRenderingContext2D): void;
+            /** @hidden */
+            _render(context: CanvasRenderingContext2D): boolean;
+            /** @hidden */
+            _draw(context: CanvasRenderingContext2D): void;
             /**
                 * Tests if a given coordinates belong to the current control
                 * @param x defines x coordinate to test
@@ -4481,6 +4533,18 @@ declare module BABYLON.GUI {
             /** Gets the list of children */
             readonly children: Control[];
             /**
+                * Gets the definition of a specific row
+                * @param index defines the index of the row
+                * @returns the row definition
+                */
+            getRowDefinition(index: number): BABYLON.Nullable<ValueAndUnit>;
+            /**
+                * Gets the definition of a specific column
+                * @param index defines the index of the column
+                * @returns the column definition
+                */
+            getColumnDefinition(index: number): BABYLON.Nullable<ValueAndUnit>;
+            /**
                 * Adds a new row to the grid
                 * @param height defines the height of the row (either in pixel or a value between 0 and 1)
                 * @param isPixel defines if the height is expressed in pixel (or in percentage)
@@ -4552,7 +4616,7 @@ declare module BABYLON.GUI {
             protected _getGridDefinitions(definitionCallback: (lefts: number[], tops: number[], widths: number[], heights: number[]) => void): void;
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             _flagDescendantsAsMatrixDirty(): void;
-            protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
     }
@@ -4626,7 +4690,8 @@ declare module BABYLON.GUI {
             protected _getTypeName(): string;
             /** Force the control to synchronize with its content */
             synchronizeSizeWithContent(): void;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             dispose(): void;
             /** STRETCH_NONE */
             static readonly STRETCH_NONE: number;
@@ -4725,7 +4790,7 @@ declare module BABYLON.GUI {
                 * @param evt Defines the KeyboardEvent
                 */
             processKeyboard(evt: KeyboardEvent): void;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
             protected _beforeRenderText(text: string): string;
@@ -4768,7 +4833,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _measure(): void;
             protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /**
@@ -4843,7 +4908,7 @@ declare module BABYLON.GUI {
             horizontalAlignment: number;
             verticalAlignment: number;
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             _measure(): void;
             protected _computeAlignment(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
@@ -4874,7 +4939,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
                 * Utility function to easily create a radio button with a header
@@ -4911,7 +4976,10 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
+            /** @hidden */
             protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _postMeasure(): void;
     }
 }
 declare module BABYLON.GUI {
@@ -5093,60 +5161,31 @@ declare module BABYLON.GUI {
         * Class used to hold a viewer window and sliders in a grid
      */
     export class ScrollViewer extends Rectangle {
-            /** name of ScrollViewer */
-            name?: string | undefined;
-            /**
-                * Adds windowContents to the grid view window
-                * @param windowContents the contents to add the grid view window
-                */
-            addToWindow(windowContents: Control): void;
-            /**
-                * Gets or sets a value indicating the padding to use on the left of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingLeft: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the left of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingLeftInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the right of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingRight: string | number;
             /**
-                * Gets a value indicating the padding in pixels to use on the right of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingRightInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the top of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            paddingTop: string | number;
-            /**
-                * Gets a value indicating the padding in pixels to use on the top of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-                */
-            readonly paddingTopInPixels: number;
-            /**
-                * Gets or sets a value indicating the padding to use on the bottom of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                * Adds a new control to the current container
+                * @param control defines the control to add
+                * @returns the current container
                 */
-            paddingBottom: string | number;
+            addControl(control: BABYLON.Nullable<Control>): Container;
             /**
-                * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
-                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                * Removes a control from the current container
+                * @param control defines the control to remove
+                * @returns the current container
                 */
-            readonly paddingBottomInPixels: number;
+            removeControl(control: Control): Container;
+            /** Gets the list of children */
+            readonly children: Control[];
+            _flagDescendantsAsMatrixDirty(): void;
             /**
              * Creates a new ScrollViewer
              * @param name of ScrollViewer
              */
-            constructor(
-            /** name of ScrollViewer */
-            name?: string | undefined);
+            constructor(name?: string);
+            /** Reset the scroll viewer window to initial size */
+            resetWindow(): void;
+            protected _getTypeName(): string;
+            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _postMeasure(): void;
             /**
                 * Gets or sets the mouse wheel precision
                 * from 0 to 1 with a default value of 0.05
@@ -5154,12 +5193,14 @@ declare module BABYLON.GUI {
             wheelPrecision: number;
             /** Gets or sets the bar color */
             barColor: string;
+            /** Gets or sets the size of the bar */
+            barSize: number;
             /** Gets or sets the bar color */
             barBorderColor: string;
             /** Gets or sets the bar background */
             barBackground: string;
-            /** @hidden */
-            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _link(host: AdvancedDynamicTexture): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
     }
@@ -5245,10 +5286,10 @@ declare module BABYLON.GUI {
                 */
             name?: string | undefined, text?: string);
             protected _getTypeName(): string;
+            protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _applyStates(context: CanvasRenderingContext2D): void;
-            protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[];
             protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
             protected _parseLineEllipsis(line: string | undefined, width: number, context: CanvasRenderingContext2D): object;
@@ -5399,7 +5440,7 @@ declare module BABYLON.GUI {
                 * @param name defines the control name
                 */
             constructor(name?: string | undefined);
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             protected _getTypeName(): string;
     }
 }
@@ -5451,6 +5492,8 @@ declare module BABYLON.GUI {
             protected _getThumbPosition(): number;
             protected _getThumbThickness(type: string): number;
             protected _prepareRenderingData(type: string): void;
+            /** @hidden */
+            protected _updateValueFromPointer(x: number, y: number): void;
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             _onPointerMove(target: Control, coordinates: BABYLON.Vector2): void;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
@@ -5462,6 +5505,9 @@ declare module BABYLON.GUI {
         */
     export class Slider extends BaseSlider {
             name?: string | undefined;
+            protected _displayValueBar: boolean;
+            /** Gets or sets a boolean indicating if the value bar must be rendered */
+            displayValueBar: boolean;
             /** Gets or sets border color */
             borderColor: string;
             /** Gets or sets background color */
@@ -5474,7 +5520,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 declare module BABYLON.GUI {
@@ -5502,7 +5548,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 declare module BABYLON.GUI {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 8 - 8
dist/preview release/inspector/babylon.inspector.bundle.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.js.map


+ 1 - 1
dist/preview release/inspector/babylon.inspector.d.ts

@@ -1,6 +1,6 @@
 /*Babylon.js Inspector*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
 declare module INSPECTOR {
 }
 declare module INSPECTOR {

+ 2 - 2
dist/preview release/inspector/babylon.inspector.module.d.ts

@@ -1,6 +1,6 @@
 /*Babylon.js Inspector*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
 
 declare module 'babylonjs-inspector' {
     export * from "babylonjs-inspector/inspector";
@@ -31,7 +31,7 @@ declare module 'babylonjs-inspector/components/propertyChangedEvent' {
 
 /*Babylon.js Inspector*/
 // Dependencies for this module:
-//   ../../../../Tools/Gulp/babylonjs
+//   ../../../../Tools/gulp/babylonjs
 declare module INSPECTOR {
 }
 declare module INSPECTOR {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylon.glTF1FileLoader.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.min.js


+ 7 - 17
dist/preview release/viewer/babylon.viewer.d.ts

@@ -2,10 +2,14 @@
 /// <reference path="./babylon.glTF2Interface.d.ts"/>
 /// <reference path="./babylonjs.loaders.d.ts"/>
 declare module "babylonjs-loaders"{ export=BABYLON;}
+/// <reference path="./babylon.d.ts"/>
+/// <reference path="./babylon.glTF2Interface.d.ts"/>
+/// <reference path="./babylonjs.loaders.d.ts"/>
+declare module "babylonjs-loaders"{ export=BABYLON;}
 // Generated by dts-bundle v0.7.3
 // Dependencies for this module:
-//   ../../../../../Tools/Gulp/babylonjs
-//   ../../../../../Tools/Gulp/babylonjs-loaders
+//   ../../../../../Tools/gulp/babylonjs
+//   ../../../../../Tools/gulp/babylonjs-loaders
 declare module BabylonViewer {
     /**
         * BabylonJS Viewer
@@ -924,7 +928,7 @@ declare module BabylonViewer {
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 declare module BabylonViewer {
@@ -1558,20 +1562,6 @@ declare module BabylonViewer {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 declare module BabylonViewer {
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-declare module BabylonViewer {
 }
 declare module BabylonViewer {
     export interface IEnvironmentMapConfiguration {

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 3 - 3
dist/preview release/viewer/babylon.viewer.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 7 - 7
dist/preview release/viewer/babylon.viewer.max.js


+ 8 - 21
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -3,11 +3,15 @@
 /// <reference path="./babylonjs.loaders.d.ts"/>
 declare module "babylonjs-loaders"{ export=BABYLON;}
 
+/// <reference path="./babylon.d.ts"/>
+/// <reference path="./babylon.glTF2Interface.d.ts"/>
+/// <reference path="./babylonjs.loaders.d.ts"/>
+declare module "babylonjs-loaders"{ export=BABYLON;}
+
 // Generated by dts-bundle v0.7.3
 // Dependencies for this module:
-//   ../../../../../Tools/Gulp/babylonjs
-//   ../../../../../Tools/Gulp/babylonjs-gltf2interface
-//   ../../../../../Tools/Gulp/babylonjs-loaders
+//   ../../../../../Tools/gulp/babylonjs
+//   ../../../../../Tools/gulp/babylonjs-loaders
 
 declare module 'babylonjs-viewer' {
     import { mapperManager } from 'babylonjs-viewer/configuration/mappers';
@@ -986,14 +990,13 @@ declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
 }
 
 declare module 'babylonjs-viewer/optimizer/custom' {
-    import { extendedUpgrade } from "babylonjs-viewer/optimizer/custom/extended";
     import { SceneManager } from "babylonjs-viewer/managers/sceneManager";
     /**
       *
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 
@@ -1664,22 +1667,6 @@ declare module 'babylonjs-viewer/loader/plugins' {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 
-declare module 'babylonjs-viewer/optimizer/custom/extended' {
-    import { SceneManager } from 'babylonjs-viewer/managers/sceneManager';
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-
 declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/cameraConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/colorGradingConfiguration';

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

@@ -19,6 +19,7 @@
   - Added new [ImageBasedSlider](http://doc.babylonjs.com/how_to/gui#imagebasedslider) to let users customize sliders using images ([Deltakosh](https://github.com/deltakosh))
   - Added support for clipboard events to let users perform `cut`, `copy` and `paste` events ([Saket Saurabh](https://github.com/ssaket))
   - Added new [ScrollViewer](https://doc.babylonjs.com/how_to/scrollviewer) with mouse wheel scrolling for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/))
+  - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
 
 ## Updates
 
@@ -92,6 +93,7 @@
 ### Materials Library
 
 ## Bug fixes
+- Fixed ArcRotateCamera control when upVector was modified ([Deltakosh](https://github.com/deltakosh))
 - Fixed anaglyph mode for Free and Universal cameras ([Deltakosh](https://github.com/deltakosh))
 - Fixed FileLoader's loading of a skybox, & added a parsed value for whether to create with PBR or STDMaterial ([Palmer-JC](https://github.com/Palmer-JC))
 - Removed bones from rootNodes where they should never have been ([Deltakosh](https://github.com/deltakosh))

+ 7 - 11
gui/src/2D/advancedDynamicTexture.ts

@@ -69,9 +69,6 @@ export class AdvancedDynamicTexture extends DynamicTexture {
     private _renderScale = 1;
     private _rootCanvas: Nullable<HTMLCanvasElement>;
 
-    /** @hidden */
-    public _needRedraw = false;
-
     /**
      * Define type to string to ensure compatibility across browsers
      * Safari doesn't support DataTransfer constructor
@@ -317,7 +314,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
             info.skipOnPointerObservable = true;
         });
 
-        this._rootContainer._link(null, this);
+        this._rootContainer._link(this);
 
         this.hasAlpha = true;
 
@@ -576,12 +573,10 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         context.font = "18px Arial";
         context.strokeStyle = "white";
         var measure = new Measure(0, 0, renderWidth, renderHeight);
-        this._rootContainer._draw(measure, context);
+        this._rootContainer._layout(measure, context);
+        this._isDirty = false; // Restoring the dirty state that could have been set by controls during layout processing
 
-        if (this._needRedraw) { // We need to redraw as some elements dynamically adapt to their content
-            this._needRedraw = false;
-            this._render();
-        }
+        this._rootContainer._render(context);
     }
 
     /** @hidden */
@@ -833,16 +828,17 @@ export class AdvancedDynamicTexture extends DynamicTexture {
     }
 
     private _attachToOnPointerOut(scene: Scene): void {
+
         this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add((pointerEvent) => {
             if (this._lastControlOver[pointerEvent.pointerId]) {
                 this._lastControlOver[pointerEvent.pointerId]._onPointerOut(this._lastControlOver[pointerEvent.pointerId]);
             }
             delete this._lastControlOver[pointerEvent.pointerId];
 
-            if (this._lastControlDown[pointerEvent.pointerId]) {
+            if (this._lastControlDown[pointerEvent.pointerId] && this._lastControlDown[pointerEvent.pointerId] !== this._capturingControl[pointerEvent.pointerId]) {
                 this._lastControlDown[pointerEvent.pointerId]._forcePointerUp();
+                delete this._lastControlDown[pointerEvent.pointerId];
             }
-            delete this._lastControlDown[pointerEvent.pointerId];
         });
     }
 

+ 6 - 1
gui/src/2D/controls/button.ts

@@ -51,12 +51,17 @@ export class Button extends Rectangle {
         this.thickness = 1;
         this.isPointerBlocker = true;
 
+        let alphaStore: Nullable<number> = null;
+
         this.pointerEnterAnimation = () => {
+            alphaStore = this.alpha;
             this.alpha -= 0.1;
         };
 
         this.pointerOutAnimation = () => {
-            this.alpha += 0.1;
+            if (alphaStore !== null) {
+                this.alpha = alphaStore;
+            }
         };
 
         this.pointerDownAnimation = () => {

+ 32 - 34
gui/src/2D/controls/checkbox.ts

@@ -1,5 +1,4 @@
 import { Control } from "./control";
-import { Measure } from "../measure";
 import { Observable, Vector2 } from "babylonjs";
 import { StackPanel } from "./stackPanel";
 import { TextBlock } from "./textBlock";
@@ -92,43 +91,42 @@ export class Checkbox extends Control {
     }
 
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-            let actualWidth = this._currentMeasure.width - this._thickness;
-            let actualHeight = this._currentMeasure.height - this._thickness;
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowColor = this.shadowColor;
-                context.shadowBlur = this.shadowBlur;
-                context.shadowOffsetX = this.shadowOffsetX;
-                context.shadowOffsetY = this.shadowOffsetY;
-            }
-
-            context.fillStyle = this._isEnabled ? this._background : this._disabledColor;
-            context.fillRect(this._currentMeasure.left + this._thickness / 2, this._currentMeasure.top + this._thickness / 2, actualWidth, actualHeight);
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowBlur = 0;
-                context.shadowOffsetX = 0;
-                context.shadowOffsetY = 0;
-            }
-
-            if (this._isChecked) {
-                context.fillStyle = this._isEnabled ? this.color : this._disabledColor;
-                let offsetWidth = actualWidth * this._checkSizeRatio;
-                let offseHeight = actualHeight * this._checkSizeRatio;
-
-                context.fillRect(this._currentMeasure.left + this._thickness / 2 + (actualWidth - offsetWidth) / 2, this._currentMeasure.top + this._thickness / 2 + (actualHeight - offseHeight) / 2, offsetWidth, offseHeight);
-            }
-
-            context.strokeStyle = this.color;
-            context.lineWidth = this._thickness;
-
-            context.strokeRect(this._currentMeasure.left + this._thickness / 2, this._currentMeasure.top + this._thickness / 2, actualWidth, actualHeight);
+        let actualWidth = this._currentMeasure.width - this._thickness;
+        let actualHeight = this._currentMeasure.height - this._thickness;
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowColor = this.shadowColor;
+            context.shadowBlur = this.shadowBlur;
+            context.shadowOffsetX = this.shadowOffsetX;
+            context.shadowOffsetY = this.shadowOffsetY;
         }
+
+        context.fillStyle = this._isEnabled ? this._background : this._disabledColor;
+        context.fillRect(this._currentMeasure.left + this._thickness / 2, this._currentMeasure.top + this._thickness / 2, actualWidth, actualHeight);
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowBlur = 0;
+            context.shadowOffsetX = 0;
+            context.shadowOffsetY = 0;
+        }
+
+        if (this._isChecked) {
+            context.fillStyle = this._isEnabled ? this.color : this._disabledColor;
+            let offsetWidth = actualWidth * this._checkSizeRatio;
+            let offseHeight = actualHeight * this._checkSizeRatio;
+
+            context.fillRect(this._currentMeasure.left + this._thickness / 2 + (actualWidth - offsetWidth) / 2, this._currentMeasure.top + this._thickness / 2 + (actualHeight - offseHeight) / 2, offsetWidth, offseHeight);
+        }
+
+        context.strokeStyle = this.color;
+        context.lineWidth = this._thickness;
+
+        context.strokeRect(this._currentMeasure.left + this._thickness / 2, this._currentMeasure.top + this._thickness / 2, actualWidth, actualHeight);
+
         context.restore();
     }
 

+ 35 - 38
gui/src/2D/controls/colorpicker.ts

@@ -1,6 +1,5 @@
 import { Control } from "./control";
 import { Color3, Observable, Vector2 } from "babylonjs";
-import { Measure } from "../measure";
 
 /** Class used to create color pickers */
 export class ColorPicker extends Control {
@@ -273,58 +272,56 @@ export class ColorPicker extends Control {
     }
 
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
 
-            var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
-            var wheelThickness = radius * .2;
-            var left = this._currentMeasure.left;
-            var top = this._currentMeasure.top;
+        var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
+        var wheelThickness = radius * .2;
+        var left = this._currentMeasure.left;
+        var top = this._currentMeasure.top;
 
-            if (!this._colorWheelCanvas || this._colorWheelCanvas.width != radius * 2) {
-                this._colorWheelCanvas = this._createColorWheelCanvas(radius, wheelThickness);
-            }
+        if (!this._colorWheelCanvas || this._colorWheelCanvas.width != radius * 2) {
+            this._colorWheelCanvas = this._createColorWheelCanvas(radius, wheelThickness);
+        }
 
-            this._updateSquareProps();
+        this._updateSquareProps();
 
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowColor = this.shadowColor;
-                context.shadowBlur = this.shadowBlur;
-                context.shadowOffsetX = this.shadowOffsetX;
-                context.shadowOffsetY = this.shadowOffsetY;
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowColor = this.shadowColor;
+            context.shadowBlur = this.shadowBlur;
+            context.shadowOffsetX = this.shadowOffsetX;
+            context.shadowOffsetY = this.shadowOffsetY;
 
-                context.fillRect(this._squareLeft, this._squareTop, this._squareSize, this._squareSize);
-            }
+            context.fillRect(this._squareLeft, this._squareTop, this._squareSize, this._squareSize);
+        }
 
-            context.drawImage(this._colorWheelCanvas, left, top);
+        context.drawImage(this._colorWheelCanvas, left, top);
 
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowBlur = 0;
-                context.shadowOffsetX = 0;
-                context.shadowOffsetY = 0;
-            }
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowBlur = 0;
+            context.shadowOffsetX = 0;
+            context.shadowOffsetY = 0;
+        }
 
-            this._drawGradientSquare(this._h,
-                this._squareLeft,
-                this._squareTop,
-                this._squareSize,
-                this._squareSize,
-                context);
+        this._drawGradientSquare(this._h,
+            this._squareLeft,
+            this._squareTop,
+            this._squareSize,
+            this._squareSize,
+            context);
 
-            var cx = this._squareLeft + this._squareSize * this._s;
-            var cy = this._squareTop + this._squareSize * (1 - this._v);
+        var cx = this._squareLeft + this._squareSize * this._s;
+        var cy = this._squareTop + this._squareSize * (1 - this._v);
 
-            this._drawCircle(cx, cy, radius * .04, context);
+        this._drawCircle(cx, cy, radius * .04, context);
 
-            var dist = radius - wheelThickness * .5;
-            cx = left + radius + Math.cos((this._h - 180) * Math.PI / 180) * dist;
-            cy = top + radius + Math.sin((this._h - 180) * Math.PI / 180) * dist;
-            this._drawCircle(cx, cy, wheelThickness * .35, context);
+        var dist = radius - wheelThickness * .5;
+        cx = left + radius + Math.cos((this._h - 180) * Math.PI / 180) * dist;
+        cy = top + radius + Math.sin((this._h - 180) * Math.PI / 180) * dist;
+        this._drawCircle(cx, cy, wheelThickness * .35, context);
 
-        }
         context.restore();
     }
 

+ 85 - 47
gui/src/2D/controls/container.ts

@@ -18,6 +18,8 @@ export class Container extends Control {
     protected _adaptWidthToChildren = false;
     /** @hidden */
     protected _adaptHeightToChildren = false;
+    /** @hidden */
+    protected _rebuildLayout = false;
 
     /** Gets or sets a boolean indicating if the container should try to adapt to its children height */
     public get adaptHeightToChildren(): boolean {
@@ -149,7 +151,7 @@ export class Container extends Control {
         if (index !== -1) {
             return this;
         }
-        control._link(this, this._host);
+        control._link(this._host);
 
         control._markAllAsDirty();
 
@@ -216,6 +218,24 @@ export class Container extends Control {
     }
 
     /** @hidden */
+    public _offsetLeft(offset: number) {
+        super._offsetLeft(offset);
+
+        for (var child of this._children) {
+            child._offsetLeft(offset);
+        }
+    }
+
+    /** @hidden */
+    public _offsetTop(offset: number) {
+        super._offsetTop(offset);
+
+        for (var child of this._children) {
+            child._offsetTop(offset);
+        }
+    }
+
+    /** @hidden */
     public _markAllAsDirty(): void {
         super._markAllAsDirty();
 
@@ -246,76 +266,99 @@ export class Container extends Control {
     }
 
     /** @hidden */
-    public _link(root: Nullable<Container>, host: AdvancedDynamicTexture): void {
-        super._link(root, host);
+    public _link(host: AdvancedDynamicTexture): void {
+        super._link(host);
 
         for (var child of this._children) {
-            child._link(this, host);
+            child._link(host);
         }
     }
 
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    protected _beforeLayout() {
+        // Do nothing
+    }
+
+    /** @hidden */
+    public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
         if (!this.isVisible || this.notRenderable) {
-            return;
+            return false;
         }
-        context.save();
-
-        this._applyStates(context);
 
-        if (this._processMeasures(parentMeasure, context)) {
+        let rebuildCount = 0;
 
-            if (this.onBeforeDrawObservable.hasObservers()) {
-                this.onBeforeDrawObservable.notifyObservers(this);
-            }
+        context.save();
 
-            this._localDraw(context);
-            this._renderHighlight(context);
+        this._applyStates(context);
 
-            if (this.clipChildren) {
-                this._clipForChildren(context);
-            }
+        this._beforeLayout();
 
+        do {
             let computedWidth = -1;
             let computedHeight = -1;
+            this._rebuildLayout = false;
+            this._processMeasures(parentMeasure, context);
 
-            for (var child of this._children) {
-                if (child.isVisible && !child.notRenderable) {
+            if (!this._isClipped) {
+                for (var child of this._children) {
                     child._tempParentMeasure.copyFrom(this._measureForChildren);
 
-                    child._draw(this._measureForChildren, context);
-                    child._renderHighlight(context);
+                    if (child._layout(this._measureForChildren, context)) {
 
-                    if (child.onAfterDrawObservable.hasObservers()) {
-                        child.onAfterDrawObservable.notifyObservers(child);
+                        if (this.adaptWidthToChildren && child._width.isPixel) {
+                            computedWidth = Math.max(computedWidth, child._currentMeasure.width);
+                        }
+                        if (this.adaptHeightToChildren && child._height.isPixel) {
+                            computedHeight = Math.max(computedHeight, child._currentMeasure.height);
+                        }
                     }
+                }
 
-                    if (this.adaptWidthToChildren && child._width.isPixel) {
-                        computedWidth = Math.max(computedWidth, child._currentMeasure.width);
+                if (this.adaptWidthToChildren && computedWidth >= 0) {
+                    if (this.width !== computedWidth + "px") {
+                        this.width = computedWidth + "px";
+                        this._rebuildLayout = true;
                     }
-                    if (this.adaptHeightToChildren && child._height.isPixel) {
-                        computedHeight = Math.max(computedHeight, child._currentMeasure.height);
+                }
+                if (this.adaptHeightToChildren && computedHeight >= 0) {
+                    if (this.height !== computedHeight + "px") {
+                        this.height = computedHeight + "px";
+                        this._rebuildLayout = true;
                     }
                 }
-            }
 
-            if (this.adaptWidthToChildren && computedWidth >= 0) {
-                if (this.width !== computedWidth + "px") {
-                    this.width = computedWidth + "px";
-                    this._host._needRedraw = true;
-                }
-            }
-            if (this.adaptHeightToChildren && computedHeight >= 0) {
-                if (this.height !== computedHeight + "px") {
-                    this.height = computedHeight + "px";
-                    this._host._needRedraw = true;
-                }
+                this._postMeasure();
             }
+            rebuildCount++;
+        }
+        while (this._rebuildLayout && rebuildCount < 3);
+
+        if (rebuildCount >= 3) {
+            BABYLON.Tools.Error(`Layout cycle detected in GUI (Container uniqueId=${this.uniqueId})`);
         }
+
         context.restore();
 
-        if (this.onAfterDrawObservable.hasObservers()) {
-            this.onAfterDrawObservable.notifyObservers(this);
+        this._isDirty = false;
+
+        return true;
+    }
+
+    protected _postMeasure() {
+        // Do nothing by default
+    }
+
+    /** @hidden */
+    public _draw(context: CanvasRenderingContext2D): void {
+
+        this._localDraw(context);
+
+        if (this.clipChildren) {
+            this._clipForChildren(context);
+        }
+
+        for (var child of this._children) {
+            child._render(context);
         }
     }
 
@@ -367,11 +410,6 @@ export class Container extends Control {
     }
 
     /** @hidden */
-    protected _clipForChildren(context: CanvasRenderingContext2D): void {
-        // DO nothing
-    }
-
-    /** @hidden */
     protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
         super._additionalProcessing(parentMeasure, context);
 

+ 147 - 72
gui/src/2D/controls/control.ts

@@ -20,8 +20,6 @@ export class Control {
     private _alphaSet = false;
     private _zIndex = 0;
     /** @hidden */
-    public _root: Nullable<Container>;
-    /** @hidden */
     public _host: AdvancedDynamicTexture;
     /** Gets or sets the control parent */
     public parent: Nullable<Container>;
@@ -45,7 +43,8 @@ export class Control {
     protected _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
     /** @hidden */
     protected _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
-    private _isDirty = true;
+    /** @hidden */
+    protected _isDirty = true;
     /** @hidden */
     public _tempParentMeasure = Measure.Empty();
     /** @hidden */
@@ -68,7 +67,6 @@ export class Control {
     protected _invertTransformMatrix = Matrix2D.Identity();
     /** @hidden */
     protected _transformedPosition = Vector2.Zero();
-    private _onlyMeasureMode = false;
     private _isMatrixDirty = true;
     private _cachedOffsetX: number;
     private _cachedOffsetY: number;
@@ -84,6 +82,10 @@ export class Control {
     private _downPointerIds: { [id: number]: boolean } = {};
     protected _isEnabled = true;
     protected _disabledColor = "#9a9a9a";
+
+    /** @hidden */
+    public _isClipped = false;
+
     /** @hidden */
     public _tag: any;
 
@@ -575,8 +577,8 @@ export class Control {
 
         this._zIndex = value;
 
-        if (this._root) {
-            this._root._reOrderControl(this);
+        if (this.parent) {
+            this.parent._reOrderControl(this);
         }
     }
 
@@ -848,6 +850,23 @@ export class Control {
         return "Control";
     }
 
+    /**
+     * Gets the first ascendant in the hierarchy of the given type
+     * @param className defines the required type
+     * @returns the ascendant or null if not found
+     */
+    public getAscendantOfClass(className: string): Nullable<Control> {
+        if (!this.parent) {
+            return null;
+        }
+
+        if (this.parent.getClassName() === className) {
+            return this.parent;
+        }
+
+        return this.parent.getAscendantOfClass(className);
+    }
+
     /** @hidden */
     public _resetFontCache(): void {
         this._fontSet = true;
@@ -916,7 +935,7 @@ export class Control {
      * @param scene defines the hosting scene
      */
     public moveToVector3(position: Vector3, scene: Scene): void {
-        if (!this._host || this._root !== this._host._rootContainer) {
+        if (!this._host || this.parent !== this._host._rootContainer) {
             Tools.Error("Cannot move a control to a vector3 if the control is not at root level");
             return;
         }
@@ -961,7 +980,7 @@ export class Control {
      * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
      */
     public linkWithMesh(mesh: Nullable<AbstractMesh>): void {
-        if (!this._host || this._root && this._root !== this._host._rootContainer) {
+        if (!this._host || this.parent && this.parent !== this._host._rootContainer) {
             if (mesh) {
                 Tools.Error("Cannot link a control to a mesh if the control is not at root level");
             }
@@ -982,7 +1001,6 @@ export class Control {
         this.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
         this.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
         this._linkedMesh = mesh;
-        this._onlyMeasureMode = this._currentMeasure.width === 0 || this._currentMeasure.height === 0;
         this._host._linkedControls.push(this);
     }
 
@@ -1012,6 +1030,18 @@ export class Control {
     }
 
     /** @hidden */
+    public _offsetLeft(offset: number) {
+        this._isDirty = true;
+        this._currentMeasure.left += offset;
+    }
+
+    /** @hidden */
+    public _offsetTop(offset: number) {
+        this._isDirty = true;
+        this._currentMeasure.top += offset;
+    }
+
+    /** @hidden */
     public _markMatrixAsDirty(): void {
         this._isMatrixDirty = true;
         this._flagDescendantsAsMatrixDirty();
@@ -1046,8 +1076,7 @@ export class Control {
     }
 
     /** @hidden */
-    public _link(root: Nullable<Container>, host: AdvancedDynamicTexture): void {
-        this._root = root;
+    public _link(host: AdvancedDynamicTexture): void {
         this._host = host;
         if (this._host) {
             this.uniqueId = this._host.getScene()!.getUniqueId();
@@ -1081,7 +1110,7 @@ export class Control {
             this._isMatrixDirty = false;
             this._flagDescendantsAsMatrixDirty();
 
-            Matrix2D.ComposeToRef(-offsetX, -offsetY, this._rotation, this._scaleX, this._scaleY, this._root ? this._root._transformMatrix : null, this._transformMatrix);
+            Matrix2D.ComposeToRef(-offsetX, -offsetY, this._rotation, this._scaleX, this._scaleY, this.parent ? this.parent._transformMatrix : null, this._transformMatrix);
 
             this._transformMatrix.invertToRef(this._invertTransformMatrix);
         }
@@ -1102,7 +1131,7 @@ export class Control {
     }
 
     /** @hidden */
-    protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
+    public _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
         context.strokeRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
     }
 
@@ -1133,9 +1162,26 @@ export class Control {
     }
 
     /** @hidden */
-    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
+    public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
+        if (!this.isVisible || this.notRenderable) {
+            return false;
+        }
+        context.save();
+
+        this._applyStates(context);
+
+        this._processMeasures(parentMeasure, context);
+
+        context.restore();
+
+        this._isDirty = false;
+
+        return true;
+    }
+
+    /** @hidden */
+    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
         if (this._isDirty || !this._cachedParentMeasure.isEqualsTo(parentMeasure)) {
-            this._isDirty = false;
             this._currentMeasure.copyFrom(parentMeasure);
 
             // Let children take some pre-measurement actions
@@ -1160,64 +1206,30 @@ export class Control {
             }
         }
 
-        if (this._currentMeasure.left > parentMeasure.left + parentMeasure.width) {
-            return false;
-        }
-
-        if (this._currentMeasure.left + this._currentMeasure.width < parentMeasure.left) {
-            return false;
-        }
-
-        if (this._currentMeasure.top > parentMeasure.top + parentMeasure.height) {
-            return false;
-        }
-
-        if (this._currentMeasure.top + this._currentMeasure.height < parentMeasure.top) {
-            return false;
-        }
-
-        // Transform
-        this._transform(context);
+        if (this.parent && this.parent.clipChildren) {
+            // Early clip
+            if (this._currentMeasure.left > parentMeasure.left + parentMeasure.width) {
+                this._isClipped = true;
+                return;
+            }
 
-        if (this._onlyMeasureMode) {
-            this._onlyMeasureMode = false;
-            return false; // We do not want rendering for this frame as they are measure dependant information that need to be gathered
-        }
+            if (this._currentMeasure.left + this._currentMeasure.width < parentMeasure.left) {
+                this._isClipped = true;
+                return;
+            }
 
-        // Clip
-        if (this.clipChildren) {
-            this._clip(context);
-            context.clip();
-        }
+            if (this._currentMeasure.top > parentMeasure.top + parentMeasure.height) {
+                this._isClipped = true;
+                return;
+            }
 
-        if (this.onBeforeDrawObservable.hasObservers()) {
-            this.onBeforeDrawObservable.notifyObservers(this);
+            if (this._currentMeasure.top + this._currentMeasure.height < parentMeasure.top) {
+                this._isClipped = true;
+                return;
+            }
         }
 
-        return true;
-    }
-
-    /** @hidden */
-    protected _clip(context: CanvasRenderingContext2D) {
-        context.beginPath();
-
-        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-            var shadowOffsetX = this.shadowOffsetX;
-            var shadowOffsetY = this.shadowOffsetY;
-            var shadowBlur = this.shadowBlur;
-
-            var leftShadowOffset = Math.min(Math.min(shadowOffsetX, 0) - shadowBlur * 2, 0);
-            var rightShadowOffset = Math.max(Math.max(shadowOffsetX, 0) + shadowBlur * 2, 0);
-            var topShadowOffset = Math.min(Math.min(shadowOffsetY, 0) - shadowBlur * 2, 0);
-            var bottomShadowOffset = Math.max(Math.max(shadowOffsetY, 0) + shadowBlur * 2, 0);
-
-            context.rect(this._currentMeasure.left + leftShadowOffset,
-                this._currentMeasure.top + topShadowOffset,
-                this._currentMeasure.width + rightShadowOffset - leftShadowOffset,
-                this._currentMeasure.height + bottomShadowOffset - topShadowOffset);
-        } else {
-            context.rect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
-        }
+        this._isClipped = false;
     }
 
     /** @hidden */
@@ -1327,7 +1339,70 @@ export class Control {
     }
 
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    protected _clipForChildren(context: CanvasRenderingContext2D): void {
+        // DO nothing
+    }
+
+    private _clip(context: CanvasRenderingContext2D) {
+        context.beginPath();
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            var shadowOffsetX = this.shadowOffsetX;
+            var shadowOffsetY = this.shadowOffsetY;
+            var shadowBlur = this.shadowBlur;
+
+            var leftShadowOffset = Math.min(Math.min(shadowOffsetX, 0) - shadowBlur * 2, 0);
+            var rightShadowOffset = Math.max(Math.max(shadowOffsetX, 0) + shadowBlur * 2, 0);
+            var topShadowOffset = Math.min(Math.min(shadowOffsetY, 0) - shadowBlur * 2, 0);
+            var bottomShadowOffset = Math.max(Math.max(shadowOffsetY, 0) + shadowBlur * 2, 0);
+
+            context.rect(this._currentMeasure.left + leftShadowOffset,
+                this._currentMeasure.top + topShadowOffset,
+                this._currentMeasure.width + rightShadowOffset - leftShadowOffset,
+                this._currentMeasure.height + bottomShadowOffset - topShadowOffset);
+        } else {
+            context.rect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+        }
+
+        context.clip();
+    }
+
+    /** @hidden */
+    public _render(context: CanvasRenderingContext2D): boolean {
+        if (!this.isVisible || this.notRenderable || this._isClipped) {
+            this._isDirty = false;
+            return false;
+        }
+        context.save();
+
+        this._applyStates(context);
+
+        // Transform
+        this._transform(context);
+
+        // Clip
+        if (this.clipChildren) {
+            this._clip(context);
+        }
+
+        if (this.onBeforeDrawObservable.hasObservers()) {
+            this.onBeforeDrawObservable.notifyObservers(this);
+        }
+
+        this._draw(context);
+        this._renderHighlight(context);
+
+        if (this.onAfterDrawObservable.hasObservers()) {
+            this.onAfterDrawObservable.notifyObservers(this);
+        }
+
+        context.restore();
+
+        return true;
+    }
+
+    /** @hidden */
+    public _draw(context: CanvasRenderingContext2D): void {
         // Do nothing
     }
 
@@ -1550,9 +1625,9 @@ export class Control {
             this._styleObserver = null;
         }
 
-        if (this._root) {
-            this._root.removeControl(this);
-            this._root = null;
+        if (this.parent) {
+            this.parent.removeControl(this);
+            this.parent = null;
         }
 
         if (this._host) {

+ 2 - 3
gui/src/2D/controls/displayGrid.ts

@@ -1,6 +1,5 @@
 
 import { Control } from ".";
-import { Measure } from "..";
 
 /** Class used to render a grid  */
 export class DisplayGrid extends Control {
@@ -147,12 +146,12 @@ export class DisplayGrid extends Control {
         super(name);
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
 
-        if (this._isEnabled && this._processMeasures(parentMeasure, context)) {
+        if (this._isEnabled) {
 
             if (this._background) {
                 context.fillStyle = this._background;

+ 37 - 5
gui/src/2D/controls/grid.ts

@@ -33,6 +33,32 @@ export class Grid extends Container {
     }
 
     /**
+     * Gets the definition of a specific row
+     * @param index defines the index of the row
+     * @returns the row definition
+     */
+    public getRowDefinition(index: number): Nullable<ValueAndUnit> {
+        if (index < 0 || index >= this._rowDefinitions.length) {
+            return null;
+        }
+
+        return this._rowDefinitions[index];
+    }
+
+    /**
+     * Gets the definition of a specific column
+     * @param index defines the index of the column
+     * @returns the column definition
+     */
+    public getColumnDefinition(index: number): Nullable<ValueAndUnit> {
+        if (index < 0 || index >= this._columnDefinitions.length) {
+            return null;
+        }
+
+        return this._columnDefinitions[index];
+    }
+
+    /**
      * Adds a new row to the grid
      * @param height defines the height of the row (either in pixel or a value between 0 and 1)
      * @param isPixel defines if the height is expressed in pixel (or in percentage)
@@ -72,6 +98,11 @@ export class Grid extends Container {
             return this;
         }
 
+        let current = this._rowDefinitions[index];
+        if (current && current.isPixel === isPixel && current.internalValue === height) {
+            return this;
+        }
+
         this._rowDefinitions[index] = new ValueAndUnit(height, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE);
 
         this._markAsDirty();
@@ -91,6 +122,11 @@ export class Grid extends Container {
             return this;
         }
 
+        let current = this._columnDefinitions[index];
+        if (current && current.isPixel === isPixel && current.internalValue === width) {
+            return this;
+        }
+
         this._columnDefinitions[index] = new ValueAndUnit(width, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE);
 
         this._markAsDirty();
@@ -388,11 +424,7 @@ export class Grid extends Container {
         }
     }
 
-    protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
-        if (!this.isHighlighted) {
-            return;
-        }
-
+    public _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
         super._renderHighlightSpecific(context);
 
         this._getGridDefinitions((lefts: number[], tops: number[], widths: number[], heights: number[]) => {

+ 51 - 35
gui/src/2D/controls/image.ts

@@ -1,6 +1,6 @@
 import { Control } from "./control";
 import { Nullable, Tools, Observable } from "babylonjs";
-import { Measure } from "../measure";
+import { Measure } from "2D";
 
 /**
  * Class used to create 2D images
@@ -267,7 +267,31 @@ export class Image extends Control {
         this.height = this._domImage.height + "px";
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        if (this._loaded) {
+            switch (this._stretch) {
+                case Image.STRETCH_NONE:
+                    break;
+                case Image.STRETCH_FILL:
+                    break;
+                case Image.STRETCH_UNIFORM:
+                    break;
+                case Image.STRETCH_EXTEND:
+                    if (this._autoScale) {
+                        this.synchronizeSizeWithContent();
+                    }
+                    if (this.parent && this.parent.parent) { // Will update root size if root is not the top root
+                        this.parent.adaptWidthToChildren = true;
+                        this.parent.adaptHeightToChildren = true;
+                    }
+                    break;
+            }
+        }
+
+        super._processMeasures(parentMeasure, context);
+    }
+
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
@@ -297,41 +321,33 @@ export class Image extends Control {
         }
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-            if (this._loaded) {
-                switch (this._stretch) {
-                    case Image.STRETCH_NONE:
-                        context.drawImage(this._domImage, x, y, width, height,
-                            this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
-                        break;
-                    case Image.STRETCH_FILL:
-                        context.drawImage(this._domImage, x, y, width, height,
-                            this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
-                        break;
-                    case Image.STRETCH_UNIFORM:
-                        var hRatio = this._currentMeasure.width / width;
-                        var vRatio = this._currentMeasure.height / height;
-                        var ratio = Math.min(hRatio, vRatio);
-                        var centerX = (this._currentMeasure.width - width * ratio) / 2;
-                        var centerY = (this._currentMeasure.height - height * ratio) / 2;
-
-                        context.drawImage(this._domImage, x, y, width, height,
-                            this._currentMeasure.left + centerX, this._currentMeasure.top + centerY, width * ratio, height * ratio);
-                        break;
-                    case Image.STRETCH_EXTEND:
-                        context.drawImage(this._domImage, x, y, width, height,
-                            this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
-                        if (this._autoScale) {
-                            this.synchronizeSizeWithContent();
-                        }
-                        if (this._root && this._root.parent) { // Will update root size if root is not the top root
-                            this._root.width = this.width;
-                            this._root.height = this.height;
-                        }
-                        break;
-                }
+        if (this._loaded) {
+            switch (this._stretch) {
+                case Image.STRETCH_NONE:
+                    context.drawImage(this._domImage, x, y, width, height,
+                        this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                    break;
+                case Image.STRETCH_FILL:
+                    context.drawImage(this._domImage, x, y, width, height,
+                        this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                    break;
+                case Image.STRETCH_UNIFORM:
+                    var hRatio = this._currentMeasure.width / width;
+                    var vRatio = this._currentMeasure.height / height;
+                    var ratio = Math.min(hRatio, vRatio);
+                    var centerX = (this._currentMeasure.width - width * ratio) / 2;
+                    var centerY = (this._currentMeasure.height - height * ratio) / 2;
+
+                    context.drawImage(this._domImage, x, y, width, height,
+                        this._currentMeasure.left + centerX, this._currentMeasure.top + centerY, width * ratio, height * ratio);
+                    break;
+                case Image.STRETCH_EXTEND:
+                    context.drawImage(this._domImage, x, y, width, height,
+                        this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                    break;
             }
         }
+
         context.restore();
     }
 

+ 4 - 4
gui/src/2D/controls/index.ts

@@ -13,13 +13,13 @@ export * from "./multiLine";
 export * from "./radioButton";
 export * from "./stackPanel";
 export * from "./selector";
-export * from "./scrollViewer";
+export * from "./scrollViewers/scrollViewer";
 export * from "./textBlock";
 export * from "./virtualKeyboard";
 export * from "./rectangle";
 export * from "./displayGrid";
-export * from "./baseSlider";
-export * from "./slider";
-export * from "./imageBasedSlider";
+export * from "./sliders/baseSlider";
+export * from "./sliders/slider";
+export * from "./sliders/imageBasedSlider";
 
 export * from "./statics";

+ 111 - 115
gui/src/2D/controls/inputText.ts

@@ -1,8 +1,7 @@
 import { Control } from "./control";
 import { IFocusableControl } from "../advancedDynamicTexture";
 import { ValueAndUnit } from "../valueAndUnit";
-import { Nullable, Observable, Observer, Vector2, ClipboardEventTypes, ClipboardInfo, PointerInfo, PointerEventTypes } from 'babylonjs';
-import { Measure } from "../measure";
+import { Nullable, Observable, Observer, Vector2, ClipboardEventTypes, ClipboardInfo, PointerInfo } from 'babylonjs';
 import { VirtualKeyboard } from "./virtualKeyboard";
 
 /**
@@ -634,146 +633,143 @@ export class InputText extends Control implements IFocusableControl {
         this.text = this._text.slice(0, insertPosition) + data + this._text.slice(insertPosition);
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowColor = this.shadowColor;
-                context.shadowBlur = this.shadowBlur;
-                context.shadowOffsetX = this.shadowOffsetX;
-                context.shadowOffsetY = this.shadowOffsetY;
-            }
-
-            // Background
-            if (this._isFocused) {
-                if (this._focusedBackground) {
-                    context.fillStyle = this._isEnabled ? this._focusedBackground : this._disabledColor;
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowColor = this.shadowColor;
+            context.shadowBlur = this.shadowBlur;
+            context.shadowOffsetX = this.shadowOffsetX;
+            context.shadowOffsetY = this.shadowOffsetY;
+        }
 
-                    context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
-                }
-            } else if (this._background) {
-                context.fillStyle = this._isEnabled ? this._background : this._disabledColor;
+        // Background
+        if (this._isFocused) {
+            if (this._focusedBackground) {
+                context.fillStyle = this._isEnabled ? this._focusedBackground : this._disabledColor;
 
                 context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
             }
+        } else if (this._background) {
+            context.fillStyle = this._isEnabled ? this._background : this._disabledColor;
 
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowBlur = 0;
-                context.shadowOffsetX = 0;
-                context.shadowOffsetY = 0;
-            }
+            context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+        }
 
-            if (!this._fontOffset) {
-                this._fontOffset = Control._GetFontOffset(context.font);
-            }
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowBlur = 0;
+            context.shadowOffsetX = 0;
+            context.shadowOffsetY = 0;
+        }
 
-            // Text
-            let clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width);
-            if (this.color) {
-                context.fillStyle = this.color;
-            }
+        if (!this._fontOffset) {
+            this._fontOffset = Control._GetFontOffset(context.font);
+        }
 
-            let text = this._beforeRenderText(this._text);
+        // Text
+        let clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, this._tempParentMeasure.width);
+        if (this.color) {
+            context.fillStyle = this.color;
+        }
 
-            if (!this._isFocused && !this._text && this._placeholderText) {
-                text = this._placeholderText;
+        let text = this._beforeRenderText(this._text);
 
-                if (this._placeholderColor) {
-                    context.fillStyle = this._placeholderColor;
-                }
-            }
+        if (!this._isFocused && !this._text && this._placeholderText) {
+            text = this._placeholderText;
 
-            this._textWidth = context.measureText(text).width;
-            let marginWidth = this._margin.getValueInPixel(this._host, parentMeasure.width) * 2;
-            if (this._autoStretchWidth) {
-                this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), this._textWidth + marginWidth) + "px";
+            if (this._placeholderColor) {
+                context.fillStyle = this._placeholderColor;
             }
+        }
 
-            let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
-            let availableWidth = this._width.getValueInPixel(this._host, parentMeasure.width) - marginWidth;
-            context.save();
-            context.beginPath();
-            context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
-            context.clip();
-
-            if (this._isFocused && this._textWidth > availableWidth) {
-                let textLeft = clipTextLeft - this._textWidth + availableWidth;
-                if (!this._scrollLeft) {
-                    this._scrollLeft = textLeft;
-                }
-            } else {
-                this._scrollLeft = clipTextLeft;
+        this._textWidth = context.measureText(text).width;
+        let marginWidth = this._margin.getValueInPixel(this._host, this._tempParentMeasure.width) * 2;
+        if (this._autoStretchWidth) {
+            this.width = Math.min(this._maxWidth.getValueInPixel(this._host, this._tempParentMeasure.width), this._textWidth + marginWidth) + "px";
+        }
+
+        let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+        let availableWidth = this._width.getValueInPixel(this._host, this._tempParentMeasure.width) - marginWidth;
+        context.save();
+        context.beginPath();
+        context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
+        context.clip();
+
+        if (this._isFocused && this._textWidth > availableWidth) {
+            let textLeft = clipTextLeft - this._textWidth + availableWidth;
+            if (!this._scrollLeft) {
+                this._scrollLeft = textLeft;
             }
+        } else {
+            this._scrollLeft = clipTextLeft;
+        }
 
-            context.fillText(text, this._scrollLeft, this._currentMeasure.top + rootY);
+        context.fillText(text, this._scrollLeft, this._currentMeasure.top + rootY);
 
-            // Cursor
-            if (this._isFocused) {
+        // Cursor
+        if (this._isFocused) {
 
-                // Need to move cursor
-                if (this._clickedCoordinate) {
-                    var rightPosition = this._scrollLeft + this._textWidth;
-                    var absoluteCursorPosition = rightPosition - this._clickedCoordinate;
-                    var currentSize = 0;
-                    this._cursorOffset = 0;
-                    var previousDist = 0;
-                    do {
-                        if (this._cursorOffset) {
-                            previousDist = Math.abs(absoluteCursorPosition - currentSize);
-                        }
-                        this._cursorOffset++;
-                        currentSize = context.measureText(text.substr(text.length - this._cursorOffset, this._cursorOffset)).width;
-
-                    } while (currentSize < absoluteCursorPosition && (text.length >= this._cursorOffset));
-
-                    // Find closest move
-                    if (Math.abs(absoluteCursorPosition - currentSize) > previousDist) {
-                        this._cursorOffset--;
+            // Need to move cursor
+            if (this._clickedCoordinate) {
+                var rightPosition = this._scrollLeft + this._textWidth;
+                var absoluteCursorPosition = rightPosition - this._clickedCoordinate;
+                var currentSize = 0;
+                this._cursorOffset = 0;
+                var previousDist = 0;
+                do {
+                    if (this._cursorOffset) {
+                        previousDist = Math.abs(absoluteCursorPosition - currentSize);
                     }
+                    this._cursorOffset++;
+                    currentSize = context.measureText(text.substr(text.length - this._cursorOffset, this._cursorOffset)).width;
 
-                    this._blinkIsEven = false;
-                    this._clickedCoordinate = null;
-                }
+                } while (currentSize < absoluteCursorPosition && (text.length >= this._cursorOffset));
 
-                // Render cursor
-                if (!this._blinkIsEven) {
-                    let cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
-                    let cursorOffsetWidth = context.measureText(cursorOffsetText).width;
-                    let cursorLeft = this._scrollLeft + this._textWidth - cursorOffsetWidth;
-
-                    if (cursorLeft < clipTextLeft) {
-                        this._scrollLeft += (clipTextLeft - cursorLeft);
-                        cursorLeft = clipTextLeft;
-                        this._markAsDirty();
-                    } else if (cursorLeft > clipTextLeft + availableWidth) {
-                        this._scrollLeft += (clipTextLeft + availableWidth - cursorLeft);
-                        cursorLeft = clipTextLeft + availableWidth;
-                        this._markAsDirty();
-                    }
-                    context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
+                // Find closest move
+                if (Math.abs(absoluteCursorPosition - currentSize) > previousDist) {
+                    this._cursorOffset--;
                 }
 
-                clearTimeout(this._blinkTimeout);
-                this._blinkTimeout = <any>setTimeout(() => {
-                    this._blinkIsEven = !this._blinkIsEven;
-                    this._markAsDirty();
-                }, 500);
+                this._blinkIsEven = false;
+                this._clickedCoordinate = null;
+            }
 
-                //show the highlighted text
-                if (this._isTextHighlightOn) {
-                    clearTimeout(this._blinkTimeout);
-                    let highlightCursorOffsetWidth = context.measureText(this.text.substring(this._startHighlightIndex)).width;
-                    let highlightCursorLeft = this._scrollLeft + this._textWidth - highlightCursorOffsetWidth;
-                    this._highlightedText = this.text.substring(this._startHighlightIndex, this._endHighlightIndex);
-                    //for transparancy
-                    context.globalAlpha = this._highligherOpacity;
-                    context.fillStyle = this._textHighlightColor;
-                    context.fillRect(highlightCursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, context.measureText(this.text.substring(this._startHighlightIndex, this._endHighlightIndex)).width, this._fontOffset.height);
-                    context.globalAlpha = 1.0;
+            // Render cursor
+            if (!this._blinkIsEven) {
+                let cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
+                let cursorOffsetWidth = context.measureText(cursorOffsetText).width;
+                let cursorLeft = this._scrollLeft + this._textWidth - cursorOffsetWidth;
+
+                if (cursorLeft < clipTextLeft) {
+                    this._scrollLeft += (clipTextLeft - cursorLeft);
+                    cursorLeft = clipTextLeft;
+                    this._markAsDirty();
+                } else if (cursorLeft > clipTextLeft + availableWidth) {
+                    this._scrollLeft += (clipTextLeft + availableWidth - cursorLeft);
+                    cursorLeft = clipTextLeft + availableWidth;
+                    this._markAsDirty();
                 }
+                context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
+            }
+
+            clearTimeout(this._blinkTimeout);
+            this._blinkTimeout = <any>setTimeout(() => {
+                this._blinkIsEven = !this._blinkIsEven;
+                this._markAsDirty();
+            }, 500);
+
+            //show the highlighted text
+            if (this._isTextHighlightOn) {
+                clearTimeout(this._blinkTimeout);
+                let highlightCursorOffsetWidth = context.measureText(this.text.substring(this._startHighlightIndex)).width;
+                let highlightCursorLeft = this._scrollLeft + this._textWidth - highlightCursorOffsetWidth;
+                this._highlightedText = this.text.substring(this._startHighlightIndex, this._endHighlightIndex);
+                //for transparancy
+                context.globalAlpha = this._highligherOpacity;
+                context.fillStyle = this._textHighlightColor;
+                context.fillRect(highlightCursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, context.measureText(this.text.substring(this._startHighlightIndex, this._endHighlightIndex)).width, this._fontOffset.height);
+                context.globalAlpha = 1.0;
             }
 
             context.restore();

+ 9 - 11
gui/src/2D/controls/line.ts

@@ -159,7 +159,7 @@ export class Line extends Control {
         return "Line";
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
@@ -170,18 +170,16 @@ export class Line extends Control {
         }
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-            context.strokeStyle = this.color;
-            context.lineWidth = this._lineWidth;
-            context.setLineDash(this._dash);
+        context.strokeStyle = this.color;
+        context.lineWidth = this._lineWidth;
+        context.setLineDash(this._dash);
 
-            context.beginPath();
-            context.moveTo(this._x1.getValue(this._host), this._y1.getValue(this._host));
+        context.beginPath();
+        context.moveTo(this._x1.getValue(this._host), this._y1.getValue(this._host));
 
-            context.lineTo(this._effectiveX2, this._effectiveY2);
+        context.lineTo(this._effectiveX2, this._effectiveY2);
 
-            context.stroke();
-        }
+        context.stroke();
 
         context.restore();
     }
@@ -204,7 +202,7 @@ export class Line extends Control {
      * @param end (opt) Set to true to assign x2 and y2 coordinates of the line. Default assign to x1 and y1.
      */
     public moveToVector3(position: Vector3, scene: Scene, end: boolean = false): void {
-        if (!this._host || this._root !== this._host._rootContainer) {
+        if (!this._host || this.parent !== this._host._rootContainer) {
             Tools.Error("Cannot move a control to a vector3 if the control is not at root level");
             return;
         }

+ 19 - 21
gui/src/2D/controls/multiLine.ts

@@ -170,7 +170,7 @@ export class MultiLine extends Control {
         return "MultiLine";
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
@@ -182,32 +182,30 @@ export class MultiLine extends Control {
 
         this._applyStates(context);
 
-        if (this._processMeasures(parentMeasure, context)) {
-            context.strokeStyle = this.color;
-            context.lineWidth = this._lineWidth;
-            context.setLineDash(this._dash);
+        context.strokeStyle = this.color;
+        context.lineWidth = this._lineWidth;
+        context.setLineDash(this._dash);
 
-            context.beginPath();
+        context.beginPath();
 
-            var first: boolean = true; //first index is not necessarily 0
+        var first: boolean = true; //first index is not necessarily 0
 
-            this._points.forEach((point) => {
-                if (!point) {
-                    return;
-                }
+        this._points.forEach((point) => {
+            if (!point) {
+                return;
+            }
 
-                if (first) {
-                    context.moveTo(point._point.x, point._point.y);
+            if (first) {
+                context.moveTo(point._point.x, point._point.y);
 
-                    first = false;
-                }
-                else {
-                    context.lineTo(point._point.x, point._point.y);
-                }
-            });
+                first = false;
+            }
+            else {
+                context.lineTo(point._point.x, point._point.y);
+            }
+        });
 
-            context.stroke();
-        }
+        context.stroke();
 
         context.restore();
     }

+ 31 - 35
gui/src/2D/controls/radioButton.ts

@@ -1,6 +1,5 @@
 import { Control } from "./control";
 import { Observable, Vector2 } from "babylonjs";
-import { Measure } from "../measure";
 import { StackPanel, TextBlock } from ".";
 
 /**
@@ -109,51 +108,48 @@ export class RadioButton extends Control {
         return "RadioButton";
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-            let actualWidth = this._currentMeasure.width - this._thickness;
-            let actualHeight = this._currentMeasure.height - this._thickness;
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowColor = this.shadowColor;
-                context.shadowBlur = this.shadowBlur;
-                context.shadowOffsetX = this.shadowOffsetX;
-                context.shadowOffsetY = this.shadowOffsetY;
-            }
-
-            // Outer
-            Control.drawEllipse(this._currentMeasure.left + this._currentMeasure.width / 2, this._currentMeasure.top + this._currentMeasure.height / 2,
-                this._currentMeasure.width / 2 - this._thickness / 2, this._currentMeasure.height / 2 - this._thickness / 2, context);
+        let actualWidth = this._currentMeasure.width - this._thickness;
+        let actualHeight = this._currentMeasure.height - this._thickness;
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowColor = this.shadowColor;
+            context.shadowBlur = this.shadowBlur;
+            context.shadowOffsetX = this.shadowOffsetX;
+            context.shadowOffsetY = this.shadowOffsetY;
+        }
 
-            context.fillStyle = this._isEnabled ? this._background : this._disabledColor;
-            context.fill();
+        // Outer
+        Control.drawEllipse(this._currentMeasure.left + this._currentMeasure.width / 2, this._currentMeasure.top + this._currentMeasure.height / 2,
+            this._currentMeasure.width / 2 - this._thickness / 2, this._currentMeasure.height / 2 - this._thickness / 2, context);
 
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowBlur = 0;
-                context.shadowOffsetX = 0;
-                context.shadowOffsetY = 0;
-            }
+        context.fillStyle = this._isEnabled ? this._background : this._disabledColor;
+        context.fill();
 
-            context.strokeStyle = this.color;
-            context.lineWidth = this._thickness;
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowBlur = 0;
+            context.shadowOffsetX = 0;
+            context.shadowOffsetY = 0;
+        }
 
-            context.stroke();
+        context.strokeStyle = this.color;
+        context.lineWidth = this._thickness;
 
-            // Inner
-            if (this._isChecked) {
-                context.fillStyle = this._isEnabled ? this.color : this._disabledColor;
-                let offsetWidth = actualWidth * this._checkSizeRatio;
-                let offseHeight = actualHeight * this._checkSizeRatio;
+        context.stroke();
 
-                Control.drawEllipse(this._currentMeasure.left + this._currentMeasure.width / 2, this._currentMeasure.top + this._currentMeasure.height / 2,
-                    offsetWidth / 2 - this._thickness / 2, offseHeight / 2 - this._thickness / 2, context);
+        // Inner
+        if (this._isChecked) {
+            context.fillStyle = this._isEnabled ? this.color : this._disabledColor;
+            let offsetWidth = actualWidth * this._checkSizeRatio;
+            let offseHeight = actualHeight * this._checkSizeRatio;
 
-                context.fill();
-            }
+            Control.drawEllipse(this._currentMeasure.left + this._currentMeasure.width / 2, this._currentMeasure.top + this._currentMeasure.height / 2,
+                offsetWidth / 2 - this._thickness / 2, offseHeight / 2 - this._thickness / 2, context);
 
+            context.fill();
         }
         context.restore();
     }

+ 0 - 435
gui/src/2D/controls/scrollViewer.ts

@@ -1,435 +0,0 @@
-import { Measure } from "../measure";
-import { Rectangle } from "./rectangle";
-import { Grid } from "./grid";
-import { Control } from "./control";
-import { Slider } from "./slider";
-import { ValueAndUnit } from "../valueAndUnit";
-import { Container } from "./container";
-import { TextBlock } from "./textBlock";
-import { PointerEventTypes, PointerInfo, Observer, Nullable } from "babylonjs";
-
-/**
- * Class used to hold a viewer window and sliders in a grid
-*/
-export class ScrollViewer extends Rectangle {
-    private _grid: Grid;
-    private _horizontalBarSpace: Rectangle;
-    private _verticalBarSpace: Rectangle;
-    private _dragSpace: Rectangle;
-    private _horizontalBar: Slider;
-    private _verticalBar: Slider;
-    private _barColor: string = "grey";
-    private _barBorderColor: string = "#444444";
-    private _barBackground: string = "white";
-    private _scrollGridWidth: number = 30;
-    private _scrollGridHeight: number = 30;
-    private _widthScale: number;
-    private _heightScale: number;
-    private _endLeft: number;
-    private _endTop: number;
-    private _window: Container;
-    private _windowContents: Control;
-    private _pointerIsOver: Boolean = false;
-    private _wheelPrecision: number = 0.05;
-    private _onPointerObserver: Nullable<Observer<PointerInfo>>;
-
-    /**
-     * Adds windowContents to the grid view window
-     * @param windowContents the contents to add the grid view window
-     */
-    public addToWindow(windowContents: Control): void {
-        this._window.removeControl(this._windowContents);
-        this._windowContents.dispose();
-        this._windowContents = windowContents;
-        if (windowContents.typeName === "TextBlock") {
-            this._updateTextBlock(windowContents);
-        }
-        else {
-            this._updateScroller(windowContents);
-        }
-        this._window.addControl(windowContents);
-    }
-
-    /**
-     * Gets or sets a value indicating the padding to use on the left of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingLeft(): string | number {
-        return this._windowContents.paddingLeft;
-    }
-
-    /**
-     * Gets a value indicating the padding in pixels to use on the left of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingLeftInPixels(): number {
-        return this._windowContents.paddingLeftInPixels;
-    }
-
-    public set paddingLeft(value: string | number) {
-        this._windowContents.paddingLeft = value;
-    }
-
-    /**
-     * Gets or sets a value indicating the padding to use on the right of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingRight(): string | number {
-        return this._windowContents.paddingRight;
-    }
-
-    /**
-     * Gets a value indicating the padding in pixels to use on the right of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingRightInPixels(): number {
-        return this._windowContents.paddingRightInPixels;
-    }
-
-    public set paddingRight(value: string | number) {
-        this._windowContents.paddingRight = value;
-    }
-
-    /**
-     * Gets or sets a value indicating the padding to use on the top of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingTop(): string | number {
-        return this._windowContents.paddingTop;
-    }
-
-    /**
-     * Gets a value indicating the padding in pixels to use on the top of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingTopInPixels(): number {
-        return this._windowContents.paddingTopInPixels;
-    }
-
-    public set paddingTop(value: string | number) {
-        this._windowContents.paddingTop = value;
-    }
-
-    /**
-     * Gets or sets a value indicating the padding to use on the bottom of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingBottom(): string | number {
-        return this._windowContents.paddingBottom;
-    }
-
-    /**
-     * Gets a value indicating the padding in pixels to use on the bottom of the viewer window
-     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
-     */
-    public get paddingBottomInPixels(): number {
-        return this._windowContents.paddingBottomInPixels;
-    }
-
-    public set paddingBottom(value: string | number) {
-        this._windowContents.paddingBottom = value;
-    }
-
-    /**
-    * Creates a new ScrollViewer
-    * @param name of ScrollViewer
-    */
-    constructor(
-        /** name of ScrollViewer */
-        public name?: string) {
-        super(name);
-
-        this.onDirtyObservable.add(() => {
-            this._horizontalBarSpace.color = this.color;
-            this._verticalBarSpace.color = this.color;
-            this._dragSpace.color = this.color;
-            this._updateScroller(this._windowContents);
-            if (this._windowContents.typeName === "TextBlock") {
-                this._updateTextBlock(this._windowContents);
-            }
-        });
-
-        this.onPointerEnterObservable.add(() => {
-            this._pointerIsOver = true;
-        });
-
-        this.onPointerOutObservable.add(() => {
-            this._pointerIsOver = false;
-        });
-
-        this._grid = new Grid();
-        this._horizontalBar = new Slider();
-        this._verticalBar = new Slider();
-
-        this._window = new Container();
-        this._window.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
-        this._window.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
-
-        this._windowContents = new Control();
-        this._window.addControl(this._windowContents);
-
-        this._width = new ValueAndUnit(0.25, ValueAndUnit.UNITMODE_PERCENTAGE, false);
-        this._height = new ValueAndUnit(0.25, ValueAndUnit.UNITMODE_PERCENTAGE, false);
-        this._background = "black";
-
-        this.fontSize = "16px";
-
-        this._grid.addColumnDefinition(1, true);
-        this._grid.addColumnDefinition(this._scrollGridWidth, true);
-        this._grid.addRowDefinition(1, true);
-        this._grid.addRowDefinition(this._scrollGridHeight, true);
-
-        this.addControl(this._grid);
-        this._grid.addControl(this._window, 0, 0);
-
-        this._verticalBar.paddingLeft = 0;
-        this._verticalBar.width = "25px";
-        this._verticalBar.value = 0;
-        this._verticalBar.maximum = 100;
-        this._verticalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
-        this._verticalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
-        this._verticalBar.left = 0.05;
-        this._verticalBar.isThumbClamped = true;
-        this._verticalBar.color = "grey";
-        this._verticalBar.borderColor = "#444444";
-        this._verticalBar.background = "white";
-        this._verticalBar.isVertical = true;
-        this._verticalBar.rotation = Math.PI;
-
-        this._verticalBarSpace = new Rectangle();
-        this._verticalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
-        this._verticalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
-        this._verticalBarSpace.color = this.color;
-        this._verticalBarSpace.thickness = 1;
-        this._grid.addControl(this._verticalBarSpace, 0, 1);
-        this._verticalBarSpace.addControl(this._verticalBar);
-
-        this._verticalBar.onValueChangedObservable.add((value) => {
-            this._window.top = value * this._endTop / 100 + "px";
-        });
-
-        this._horizontalBar.paddingLeft = 0;
-        this._horizontalBar.height = "25px";
-        this._horizontalBar.value = 0;
-        this._horizontalBar.maximum = 100;
-        this._horizontalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
-        this._horizontalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
-        this._horizontalBar.left = 0.05;
-        this._horizontalBar.isThumbClamped = true;
-        this._horizontalBar.color = "grey";
-        this._horizontalBar.borderColor = "#444444";
-        this._horizontalBar.background = "white";
-
-        this._horizontalBarSpace = new Rectangle();
-        this._horizontalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
-        this._horizontalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
-        this._horizontalBarSpace.color = this.color;
-        this._horizontalBarSpace.thickness = 1;
-        this._grid.addControl(this._horizontalBarSpace, 1, 0);
-        this._horizontalBarSpace.addControl(this._horizontalBar);
-
-        this._horizontalBar.onValueChangedObservable.add((value) => {
-            this._window.left = value * this._endLeft / 100 + "px";
-        });
-
-        this._dragSpace = new Rectangle();
-        this._dragSpace.color = this.color;
-        this._dragSpace.thickness = 2;
-        this._dragSpace.background = this._barColor;
-        this._grid.addControl(this._dragSpace, 1, 1);
-    }
-
-    /**
-     * Gets or sets the mouse wheel precision
-     * from 0 to 1 with a default value of 0.05
-     * */
-    public get wheelPrecision(): number {
-        return this._wheelPrecision;
-    }
-
-    public set wheelPrecision(value: number) {
-        if (this._wheelPrecision === value) {
-            return;
-        }
-
-        if (value < 0) {
-            value = 0;
-        }
-
-        if (value > 1) {
-            value = 1;
-        }
-
-        this._wheelPrecision = value;
-    }
-
-    /** Gets or sets the bar color */
-    public get barColor(): string {
-        return this._barColor;
-    }
-
-    public set barColor(color: string) {
-        if (this._barColor === color) {
-            return;
-        }
-
-        this._barColor = color;
-        this._horizontalBar.color = color;
-        this._verticalBar.color = color;
-        this._dragSpace.background = color;
-    }
-
-    /** Gets or sets the bar color */
-    public get barBorderColor(): string {
-        return this._barBorderColor;
-    }
-
-    public set barBorderColor(color: string) {
-        if (this._barBorderColor === color) {
-            return;
-        }
-
-        this._barBorderColor = color;
-        this._horizontalBar.borderColor = color;
-        this._verticalBar.borderColor = color;
-    }
-
-    /** Gets or sets the bar background */
-    public get barBackground(): string {
-        return this._barBackground;
-    }
-
-    public set barBackground(color: string) {
-        if (this._barBackground === color) {
-            return;
-        }
-
-        this._barBackground = color;
-        this._horizontalBar.background = color;
-        this._verticalBar.background = color;
-    }
-
-    /** @hidden */
-    protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
-        super._additionalProcessing(parentMeasure, context);
-
-        let viewerWidth = this._width.getValueInPixel(this._host, parentMeasure.width);
-        let viewerHeight = this._height.getValueInPixel(this._host, parentMeasure.height);
-
-        let innerWidth = viewerWidth - this._scrollGridWidth - 2 * this.thickness;
-        let innerHeight = viewerHeight  - this._scrollGridHeight - 2 * this.thickness;
-        this._horizontalBar.width = (innerWidth * 0.8) + "px";
-        this._verticalBar.height = (innerHeight * 0.8) + "px";
-
-        this._grid.setColumnDefinition(0, innerWidth, true);
-        this._grid.setRowDefinition(0, innerHeight, true);
-
-        this._attachWheel();
-    }
-
-    /** @hidden */
-    private _updateScroller(windowContents: Control): void {
-
-        let windowContentsWidth: number  = parseFloat(windowContents.width.toString());
-        if (windowContents._width.unit === 0) {
-            this._widthScale = windowContentsWidth / 100;
-            windowContentsWidth = this._host.getSize().width * this._widthScale;
-            windowContents.width = windowContentsWidth + "px";
-        }
-
-        let windowContentsHeight: number  = parseFloat(windowContents.height.toString());
-        if (windowContents._height.unit === 0) {
-            this._heightScale = windowContentsHeight / 100;
-            windowContentsHeight = this._host.getSize().height * this._heightScale;
-            windowContents.height = this._host.getSize().height * this._heightScale + "px";
-        }
-
-        this._window.width = windowContents.width;
-        this._window.height = windowContents.height;
-        this._windowContents.width = windowContents.width;
-        this._windowContents.height = windowContents.height;
-
-        let viewerWidth = this._width.getValueInPixel(this._host, this._host.getSize().width);
-        let viewerHeight = this._height.getValueInPixel(this._host, this._host.getSize().height);
-
-        let innerWidth = viewerWidth - this._scrollGridWidth - 2 * this.thickness;
-        let innerHeight = viewerHeight  - this._scrollGridHeight - 2 * this.thickness;
-
-        if (windowContentsWidth <= innerWidth) {
-            this._grid.setRowDefinition(0, viewerHeight - 2 * this.thickness , true);
-            this._grid.setRowDefinition(1, 0, true);
-            this._horizontalBar.isVisible = false;
-        }
-        else {
-            this._grid.setRowDefinition(0, innerHeight, true);
-            this._grid.setRowDefinition(1, this._scrollGridHeight, true);
-            this._horizontalBar.isVisible = true;
-        }
-
-        if (windowContentsHeight < innerHeight) {
-            this._grid.setColumnDefinition(0, viewerWidth - 2 * this.thickness, true);
-            this._grid.setColumnDefinition(1, 0, true);
-            this._verticalBar.isVisible = false;
-        }
-        else {
-            this._grid.setColumnDefinition(0, innerWidth, true);
-            this._grid.setColumnDefinition(1, this._scrollGridWidth, true);
-            this._verticalBar.isVisible = true;
-        }
-
-        this._endLeft = innerWidth - windowContentsWidth;
-        this._endTop = innerHeight - windowContentsHeight;
-    }
-
-    /** @hidden */
-    private _updateTextBlock(windowContents: Control): void {
-        let viewerWidth = this._width.getValueInPixel(this._host, this._host.getSize().width);
-        let innerWidth = viewerWidth - this._scrollGridWidth - 2 * this.thickness;
-
-        windowContents.width = innerWidth + "px";
-
-        this._window.width = windowContents.width;
-        this._windowContents.width = windowContents.width;
-
-        (<TextBlock>windowContents).onLinesReadyObservable.add(() => {
-            let windowContentsHeight = (this.fontOffset.height) * (<TextBlock>windowContents).lines.length + windowContents.paddingTopInPixels + windowContents.paddingBottomInPixels;
-            windowContents.height = windowContentsHeight + "px";
-            this._window.height = windowContents.height;
-            this._windowContents.height = windowContents.height;
-            this._updateScroller(windowContents);
-        });
-    }
-
-    /** @hidden */
-    private _attachWheel() {
-        let scene = this._host.getScene();
-        this._onPointerObserver = scene!.onPointerObservable.add((pi, state) => {
-            if (!this._pointerIsOver || pi.type !== PointerEventTypes.POINTERWHEEL) {
-                return;
-            }
-            if (this._verticalBar.isVisible == true) {
-                if ((<MouseWheelEvent>pi.event).deltaY < 0 && this._verticalBar.value > 0) {
-                    this._verticalBar.value -= this._wheelPrecision * 100;
-                } else if ((<MouseWheelEvent>pi.event).deltaY > 0 && this._verticalBar.value < this._verticalBar.maximum) {
-                    this._verticalBar.value += this._wheelPrecision * 100;
-                }
-            }
-            if (this._horizontalBar.isVisible == true) {
-                if ((<MouseWheelEvent>pi.event).deltaX < 0 && this._horizontalBar.value < this._horizontalBar.maximum) {
-                    this._horizontalBar.value += this._wheelPrecision * 100;
-                } else if ((<MouseWheelEvent>pi.event).deltaX > 0 && this._horizontalBar.value > 0) {
-                    this._horizontalBar.value -= this._wheelPrecision * 100;
-                }
-            }
-        });
-    }
-
-    /** Releases associated resources */
-    public dispose() {
-        let scene = this._host.getScene();
-        if (scene && this._onPointerObserver) {
-            scene.onPointerObservable.remove(this._onPointerObserver);
-        }
-        super.dispose();
-    }
-}

+ 375 - 0
gui/src/2D/controls/scrollViewers/scrollViewer.ts

@@ -0,0 +1,375 @@
+import { Rectangle } from "../rectangle";
+import { Grid } from "../grid";
+import { Control } from "../control";
+import { Container } from "../container";
+import { PointerInfo, Observer, Nullable } from "babylonjs";
+import { AdvancedDynamicTexture, Measure } from "2D";
+import { _ScrollViewerWindow } from "./scrollViewerWindow";
+import { ScrollBar } from "../sliders/scrollBar";
+
+/**
+ * Class used to hold a viewer window and sliders in a grid
+*/
+export class ScrollViewer extends Rectangle {
+    private _grid: Grid;
+    private _horizontalBarSpace: Rectangle;
+    private _verticalBarSpace: Rectangle;
+    private _dragSpace: Rectangle;
+    private _horizontalBar: ScrollBar;
+    private _verticalBar: ScrollBar;
+    private _barColor: string;
+    private _barBorderColor: string;
+    private _barBackground: string ;
+    private _barSize: number = 20;
+    private _endLeft: number;
+    private _endTop: number;
+    private _window: _ScrollViewerWindow;
+    private _pointerIsOver: Boolean = false;
+    private _wheelPrecision: number = 0.05;
+    private _onPointerObserver: Nullable<Observer<PointerInfo>>;
+    private _clientWidth: number;
+    private _clientHeight: number;
+
+    /**
+     * Adds a new control to the current container
+     * @param control defines the control to add
+     * @returns the current container
+     */
+    public addControl(control: Nullable<Control>): Container {
+        if (!control) {
+            return this;
+        }
+
+        this._window.addControl(control);
+
+        return this;
+    }
+
+    /**
+     * Removes a control from the current container
+     * @param control defines the control to remove
+     * @returns the current container
+     */
+    public removeControl(control: Control): Container {
+        this._window.removeControl(control);
+        return this;
+    }
+
+    /** Gets the list of children */
+    public get children(): Control[] {
+        return this._window.children;
+    }
+
+    public _flagDescendantsAsMatrixDirty(): void {
+        for (var child of this._children) {
+            child._markMatrixAsDirty();
+        }
+    }
+
+    /**
+    * Creates a new ScrollViewer
+    * @param name of ScrollViewer
+    */
+    constructor(name?: string) {
+        super(name);
+
+        this.onDirtyObservable.add(() => {
+            this._horizontalBarSpace.color = this.color;
+            this._verticalBarSpace.color = this.color;
+            this._dragSpace.color = this.color;
+        });
+
+        this.onPointerEnterObservable.add(() => {
+            this._pointerIsOver = true;
+        });
+
+        this.onPointerOutObservable.add(() => {
+            this._pointerIsOver = false;
+        });
+
+        this._grid = new Grid();
+        this._horizontalBar = new ScrollBar();
+        this._verticalBar = new ScrollBar();
+
+        this._window = new _ScrollViewerWindow();
+        this._window.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+        this._window.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+
+        this._grid.addColumnDefinition(1);
+        this._grid.addColumnDefinition(0, true);
+        this._grid.addRowDefinition(1);
+        this._grid.addRowDefinition(0, true);
+
+        super.addControl(this._grid);
+        this._grid.addControl(this._window, 0, 0);
+
+        this._verticalBar.paddingLeft = 0;
+        this._verticalBar.width = "100%";
+        this._verticalBar.height = "100%";
+        this._verticalBar.barOffset = 0;
+        this._verticalBar.value = 0;
+        this._verticalBar.maximum = 1;
+        this._verticalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
+        this._verticalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
+        this._verticalBar.isVertical = true;
+        this._verticalBar.rotation = Math.PI;
+        this._verticalBar.isVisible = false;
+
+        this._verticalBarSpace = new Rectangle();
+        this._verticalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+        this._verticalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+        this._verticalBarSpace.thickness = 1;
+        this._grid.addControl(this._verticalBarSpace, 0, 1);
+        this._verticalBarSpace.addControl(this._verticalBar);
+
+        this._verticalBar.onValueChangedObservable.add((value) => {
+            this._window.top = value * this._endTop + "px";
+        });
+
+        this._horizontalBar.paddingLeft = 0;
+        this._horizontalBar.width = "100%";
+        this._horizontalBar.height = "100%";
+        this._horizontalBar.barOffset = 0;
+        this._horizontalBar.value = 0;
+        this._horizontalBar.maximum = 1;
+        this._horizontalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
+        this._horizontalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
+        this._horizontalBar.isVisible = false;
+
+        this._horizontalBarSpace = new Rectangle();
+        this._horizontalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+        this._horizontalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+        this._horizontalBarSpace.thickness = 1;
+        this._grid.addControl(this._horizontalBarSpace, 1, 0);
+        this._horizontalBarSpace.addControl(this._horizontalBar);
+
+        this._horizontalBar.onValueChangedObservable.add((value) => {
+            this._window.left = value * this._endLeft + "px";
+        });
+
+        this._dragSpace = new Rectangle();
+        this._dragSpace.thickness = 1;
+        this._grid.addControl(this._dragSpace, 1, 1);
+
+        // Colors
+        this.barColor = "grey";
+        this.barBackground = "transparent";
+    }
+
+    /** Reset the scroll viewer window to initial size */
+    public resetWindow() {
+        this._window.width = "100%";
+        this._window.height = "100%";
+    }
+
+    protected _getTypeName(): string {
+        return "ScrollViewer";
+    }
+
+    private _buildClientSizes() {
+        this._window.parentClientWidth = this._currentMeasure.width - (this._verticalBar.isVisible ? this._barSize : 0) - 2 * this.thickness;
+        this._window.parentClientHeight = this._currentMeasure.height - (this._horizontalBar.isVisible ? this._barSize : 0) - 2 * this.thickness;
+
+        this._clientWidth = this._window.parentClientWidth;
+        this._clientHeight = this._window.parentClientHeight;
+    }
+
+    protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        super._additionalProcessing(parentMeasure, context);
+
+        this._buildClientSizes();
+    }
+
+    protected _postMeasure(): void {
+        super._postMeasure();
+
+        this._updateScroller();
+    }
+
+    /**
+     * Gets or sets the mouse wheel precision
+     * from 0 to 1 with a default value of 0.05
+     * */
+    public get wheelPrecision(): number {
+        return this._wheelPrecision;
+    }
+
+    public set wheelPrecision(value: number) {
+        if (this._wheelPrecision === value) {
+            return;
+        }
+
+        if (value < 0) {
+            value = 0;
+        }
+
+        if (value > 1) {
+            value = 1;
+        }
+
+        this._wheelPrecision = value;
+    }
+
+    /** Gets or sets the bar color */
+    public get barColor(): string {
+        return this._barColor;
+    }
+
+    public set barColor(color: string) {
+        if (this._barColor === color) {
+            return;
+        }
+
+        this._barColor = color;
+        this._horizontalBar.color = color;
+        this._verticalBar.color = color;
+    }
+
+    /** Gets or sets the size of the bar */
+    public get barSize(): number {
+        return this._barSize;
+    }
+
+    public set barSize(value: number) {
+        if (this._barSize === value) {
+            return;
+        }
+
+        this._barSize = value;
+        this._markAsDirty();
+
+        if (this._horizontalBar.isVisible) {
+            this._grid.setRowDefinition(1, this._barSize, true);
+        }
+        if (this._verticalBar.isVisible) {
+            this._grid.setColumnDefinition(1, this._barSize, true);
+        }
+    }
+
+    /** Gets or sets the bar color */
+    public get barBorderColor(): string {
+        return this._barBorderColor;
+    }
+
+    public set barBorderColor(color: string) {
+        if (this._barBorderColor === color) {
+            return;
+        }
+
+        this._barBorderColor = color;
+        this._horizontalBar.borderColor = color;
+        this._verticalBar.borderColor = color;
+    }
+
+    /** Gets or sets the bar background */
+    public get barBackground(): string {
+        return this._barBackground;
+    }
+
+    public set barBackground(color: string) {
+        if (this._barBackground === color) {
+            return;
+        }
+
+        this._barBackground = color;
+        this._horizontalBar.background = color;
+        this._verticalBar.background = color;
+        this._dragSpace.background = color;
+    }
+
+    /** @hidden */
+    private _updateScroller(): void {
+        let windowContentsWidth = this._window._currentMeasure.width;
+        let windowContentsHeight = this._window._currentMeasure.height;
+
+        if (this._horizontalBar.isVisible && windowContentsWidth <= this._clientWidth) {
+            this._grid.setRowDefinition(1, 0, true);
+            this._horizontalBar.isVisible = false;
+            this._horizontalBar.value = 0;
+            this._rebuildLayout = true;
+        }
+        else if (!this._horizontalBar.isVisible && windowContentsWidth > this._clientWidth) {
+            this._grid.setRowDefinition(1, this._barSize, true);
+            this._horizontalBar.isVisible = true;
+            this._rebuildLayout = true;
+        }
+
+        if (this._verticalBar.isVisible && windowContentsHeight <= this._clientHeight) {
+            this._grid.setColumnDefinition(1, 0, true);
+            this._verticalBar.isVisible = false;
+            this._verticalBar.value = 0;
+            this._rebuildLayout = true;
+        }
+        else if (!this._verticalBar.isVisible && windowContentsHeight > this._clientHeight) {
+            this._grid.setColumnDefinition(1, this._barSize, true);
+            this._verticalBar.isVisible = true;
+            this._rebuildLayout = true;
+        }
+
+        this._buildClientSizes();
+        this._endLeft = this._clientWidth - windowContentsWidth;
+        this._endTop = this._clientHeight - windowContentsHeight;
+
+        let horizontalMultiplicator = this._clientWidth / windowContentsWidth;
+        let verticalMultiplicator = this._clientHeight / windowContentsHeight;
+
+        this._horizontalBar.thumbWidth = (this._clientWidth * horizontalMultiplicator) + "px";
+        this._verticalBar.thumbWidth = (this._clientHeight * verticalMultiplicator) + "px";
+    }
+
+    public _link(host: AdvancedDynamicTexture): void {
+        super._link(host);
+
+        this._attachWheel();
+    }
+
+    /** @hidden */
+    private _attachWheel() {
+        if (this._onPointerObserver) {
+            return;
+        }
+
+        let scene = this._host.getScene();
+        this._onPointerObserver = scene!.onPointerObservable.add((pi, state) => {
+            if (!this._pointerIsOver || pi.type !== BABYLON.PointerEventTypes.POINTERWHEEL) {
+                return;
+            }
+            if (this._verticalBar.isVisible == true) {
+                if ((<MouseWheelEvent>pi.event).deltaY < 0 && this._verticalBar.value > 0) {
+                    this._verticalBar.value -= this._wheelPrecision;
+                } else if ((<MouseWheelEvent>pi.event).deltaY > 0 && this._verticalBar.value < this._verticalBar.maximum) {
+                    this._verticalBar.value += this._wheelPrecision;
+                }
+            }
+            if (this._horizontalBar.isVisible == true) {
+                if ((<MouseWheelEvent>pi.event).deltaX < 0 && this._horizontalBar.value < this._horizontalBar.maximum) {
+                    this._horizontalBar.value += this._wheelPrecision;
+                } else if ((<MouseWheelEvent>pi.event).deltaX > 0 && this._horizontalBar.value > 0) {
+                    this._horizontalBar.value -= this._wheelPrecision;
+                }
+            }
+        });
+    }
+
+    public _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
+        if (!this.isHighlighted) {
+            return;
+        }
+
+        super._renderHighlightSpecific(context);
+
+        this._grid._renderHighlightSpecific(context);
+
+        context.restore();
+    }
+
+    /** Releases associated resources */
+    public dispose() {
+        let scene = this._host.getScene();
+        if (scene && this._onPointerObserver) {
+            scene.onPointerObservable.remove(this._onPointerObserver);
+            this._onPointerObserver  = null;
+        }
+        super.dispose();
+    }
+}

+ 74 - 0
gui/src/2D/controls/scrollViewers/scrollViewerWindow.ts

@@ -0,0 +1,74 @@
+import { Measure } from "../../measure";
+import { Container } from "../container";
+import { ValueAndUnit } from "../../valueAndUnit";
+import { Control } from "../control";
+
+/**
+ * Class used to hold a the container for ScrollViewer
+ * @hidden
+*/
+export class _ScrollViewerWindow extends Container {
+    public parentClientWidth: number;
+    public parentClientHeight: number;
+
+    /**
+    * Creates a new ScrollViewerWindow
+    * @param name of ScrollViewerWindow
+    */
+    constructor(name?: string) {
+        super(name);
+    }
+
+    protected _getTypeName(): string {
+        return "ScrollViewerWindow";
+    }
+
+    /** @hidden */
+    protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        super._additionalProcessing(parentMeasure, context);
+
+        this._measureForChildren.left = this._currentMeasure.left;
+        this._measureForChildren.top = this._currentMeasure.top;
+
+        this._measureForChildren.width = parentMeasure.width;
+        this._measureForChildren.height = parentMeasure.height;
+    }
+
+    protected _postMeasure(): void {
+        var maxWidth = this.parentClientWidth;
+        var maxHeight = this.parentClientHeight;
+        for (var child of this.children) {
+            if (!child.isVisible || child.notRenderable) {
+                continue;
+            }
+
+            if (child.horizontalAlignment === Control.HORIZONTAL_ALIGNMENT_CENTER) {
+                child._offsetLeft(this._currentMeasure.left - child._currentMeasure.left);
+            }
+
+            if (child.verticalAlignment === Control.VERTICAL_ALIGNMENT_CENTER) {
+                child._offsetTop(this._currentMeasure.top - child._currentMeasure.top);
+            }
+
+            maxWidth = Math.max(maxWidth, child._currentMeasure.left - this._currentMeasure.left + child._currentMeasure.width);
+            maxHeight = Math.max(maxHeight, child._currentMeasure.top - this._currentMeasure.top + child._currentMeasure.height);
+        }
+
+        if (this._currentMeasure.width !== maxWidth) {
+            this._width.updateInPlace(maxWidth, ValueAndUnit.UNITMODE_PIXEL);
+            this._currentMeasure.width = maxWidth;
+            this._rebuildLayout = true;
+            this._isDirty = true;
+        }
+
+        if (this._currentMeasure.height !== maxHeight) {
+            this._height.updateInPlace(maxHeight, ValueAndUnit.UNITMODE_PIXEL);
+            this._currentMeasure.height = maxHeight;
+            this._rebuildLayout = true;
+            this._isDirty = true;
+        }
+
+        super._postMeasure();
+    }
+
+}

+ 1 - 1
gui/src/2D/controls/selector.ts

@@ -4,7 +4,7 @@ import { Control } from "./control";
 import { TextBlock } from "./textBlock";
 import { Checkbox } from "./checkbox";
 import { RadioButton } from "./radioButton";
-import { Slider } from "./slider";
+import { Slider } from "./sliders/slider";
 import { Container } from "./container";
 
 /** Class used to create a RadioGroup

+ 0 - 227
gui/src/2D/controls/slider.ts

@@ -1,227 +0,0 @@
-import { Measure } from "../measure";
-import { BaseSlider } from "./baseSlider";
-
-/**
- * Class used to create slider controls
- */
-export class Slider extends BaseSlider {
-    private _background = "black";
-    private _borderColor = "white";
-    private _isThumbCircle = false;
-
-    /** Gets or sets border color */
-    public get borderColor(): string {
-        return this._borderColor;
-    }
-
-    public set borderColor(value: string) {
-        if (this._borderColor === value) {
-            return;
-        }
-
-        this._borderColor = value;
-        this._markAsDirty();
-    }
-
-    /** Gets or sets background color */
-    public get background(): string {
-        return this._background;
-    }
-
-    public set background(value: string) {
-        if (this._background === value) {
-            return;
-        }
-
-        this._background = value;
-        this._markAsDirty();
-    }
-
-    /** Gets or sets a boolean indicating if the thumb should be round or square */
-    public get isThumbCircle(): boolean {
-        return this._isThumbCircle;
-    }
-
-    public set isThumbCircle(value: boolean) {
-        if (this._isThumbCircle === value) {
-            return;
-        }
-
-        this._isThumbCircle = value;
-        this._markAsDirty();
-    }
-
-    /**
-     * Creates a new Slider
-     * @param name defines the control name
-     */
-    constructor(public name?: string) {
-        super(name);
-    }
-
-    protected _getTypeName(): string {
-        return "Slider";
-    }
-
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
-        context.save();
-
-        this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-
-            this._prepareRenderingData(this.isThumbCircle ? "circle" : "rectangle");
-            var left = this._renderLeft;
-            var top = this._renderTop;
-            var width = this._renderWidth;
-            var height = this._renderHeight;
-
-            var radius = 0;
-
-            if (this.isThumbClamped && this.isThumbCircle) {
-                if (this.isVertical) {
-                    top += (this._effectiveThumbThickness / 2);
-                }
-                else {
-                    left += (this._effectiveThumbThickness / 2);
-                }
-
-                radius = this._backgroundBoxThickness / 2;
-            }
-            else {
-                radius = (this._effectiveThumbThickness - this._effectiveBarOffset) / 2;
-            }
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowColor = this.shadowColor;
-                context.shadowBlur = this.shadowBlur;
-                context.shadowOffsetX = this.shadowOffsetX;
-                context.shadowOffsetY = this.shadowOffsetY;
-            }
-
-            const thumbPosition = this._getThumbPosition();
-            context.fillStyle = this._background;
-
-            if (this.isVertical) {
-                if (this.isThumbClamped) {
-                    if (this.isThumbCircle) {
-                        context.beginPath();
-                        context.arc(left + this._backgroundBoxThickness / 2, top, radius, Math.PI, 2 * Math.PI);
-                        context.fill();
-                        context.fillRect(left, top, width, height);
-                    }
-                    else {
-                        context.fillRect(left, top, width, height + this._effectiveThumbThickness);
-                    }
-                }
-                else {
-                    context.fillRect(left, top, width, height);
-                }
-            }
-            else {
-                if (this.isThumbClamped) {
-                    if (this.isThumbCircle) {
-                        context.beginPath();
-                        context.arc(left + this._backgroundBoxLength, top + (this._backgroundBoxThickness / 2), radius, 0, 2 * Math.PI);
-                        context.fill();
-                        context.fillRect(left, top, width, height);
-                    }
-                    else {
-                        context.fillRect(left, top, width + this._effectiveThumbThickness, height);
-                    }
-                }
-                else {
-                    context.fillRect(left, top, width, height);
-                }
-            }
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowBlur = 0;
-                context.shadowOffsetX = 0;
-                context.shadowOffsetY = 0;
-            }
-
-            // Value bar
-            context.fillStyle = this.color;
-            if (this.isVertical) {
-                if (this.isThumbClamped) {
-                    if (this.isThumbCircle) {
-                        context.beginPath();
-                        context.arc(left + this._backgroundBoxThickness / 2, top + this._backgroundBoxLength, radius, 0, 2 * Math.PI);
-                        context.fill();
-                        context.fillRect(left, top + thumbPosition, width, height - thumbPosition);
-                    }
-                    else {
-                        context.fillRect(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
-                    }
-                }
-                else {
-                    context.fillRect(left, top + thumbPosition, width, height - thumbPosition);
-                }
-            }
-            else {
-                if (this.isThumbClamped) {
-                    if (this.isThumbCircle) {
-                        context.beginPath();
-                        context.arc(left, top + this._backgroundBoxThickness / 2, radius, 0, 2 * Math.PI);
-                        context.fill();
-                        context.fillRect(left, top, thumbPosition, height);
-                    }
-                    else {
-                        context.fillRect(left, top, thumbPosition, height);
-                    }
-                }
-                else {
-                    context.fillRect(left, top, thumbPosition, height);
-                }
-            }
-
-            // Thumb
-            if (this.displayThumb) {
-                if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                    context.shadowColor = this.shadowColor;
-                    context.shadowBlur = this.shadowBlur;
-                    context.shadowOffsetX = this.shadowOffsetX;
-                    context.shadowOffsetY = this.shadowOffsetY;
-                }
-                if (this._isThumbCircle) {
-                    context.beginPath();
-                    if (this.isVertical) {
-                        context.arc(left + this._backgroundBoxThickness / 2, top + thumbPosition, radius, 0, 2 * Math.PI);
-                    }
-                    else {
-                        context.arc(left + thumbPosition, top + (this._backgroundBoxThickness / 2), radius, 0, 2 * Math.PI);
-                    }
-                    context.fill();
-                    if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                        context.shadowBlur = 0;
-                        context.shadowOffsetX = 0;
-                        context.shadowOffsetY = 0;
-                    }
-                    context.strokeStyle = this._borderColor;
-                    context.stroke();
-                }
-                else {
-                    if (this.isVertical) {
-                        context.fillRect(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
-                    }
-                    else {
-                        context.fillRect(this._currentMeasure.left + thumbPosition, this._currentMeasure.top, this._effectiveThumbThickness, this._currentMeasure.height);
-                    }
-                    if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                        context.shadowBlur = 0;
-                        context.shadowOffsetX = 0;
-                        context.shadowOffsetY = 0;
-                    }
-                    context.strokeStyle = this._borderColor;
-                    if (this.isVertical) {
-                        context.strokeRect(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
-                    }
-                    else {
-                        context.strokeRect(this._currentMeasure.left + thumbPosition, this._currentMeasure.top, this._effectiveThumbThickness, this._currentMeasure.height);
-                    }
-                }
-            }
-        }
-        context.restore();
-    }
-}

+ 4 - 3
gui/src/2D/controls/baseSlider.ts

@@ -1,5 +1,5 @@
-import { Control } from "./control";
-import { ValueAndUnit } from "../valueAndUnit";
+import { Control } from "../control";
+import { ValueAndUnit } from "../../valueAndUnit";
 import { Observable, Vector2 } from "babylonjs";
 
 /**
@@ -255,7 +255,8 @@ export class BaseSlider extends Control {
     // Events
     private _pointerIsDown = false;
 
-    private _updateValueFromPointer(x: number, y: number): void {
+    /** @hidden */
+    protected _updateValueFromPointer(x: number, y: number): void {
         if (this.rotation != 0) {
             this._invertTransformMatrix.transformCoordinates(x, y, this._transformedPosition);
             x = this._transformedPosition.x;

+ 46 - 44
gui/src/2D/controls/imageBasedSlider.ts

@@ -1,6 +1,6 @@
 import { BaseSlider } from "./baseSlider";
-import { Measure } from "../measure";
-import { Image } from "./image";
+import { Measure } from "../../measure";
+import { Image } from "../image";
 
 /**
  * Class used to create slider controls based on images
@@ -100,60 +100,62 @@ export class ImageBasedSlider extends BaseSlider {
         return "ImageBasedSlider";
     }
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
-        if (this._processMeasures(parentMeasure, context)) {
-
-            this._prepareRenderingData("rectangle");
-            const thumbPosition = this._getThumbPosition();
-            var left = this._renderLeft;
-            var top = this._renderTop;
-            var width = this._renderWidth;
-            var height = this._renderHeight;
-
-            // Background
-            if (this._backgroundImage) {
-                this._tempMeasure.copyFromFloats(left, top, width, height);
-                if (this.isThumbClamped && this.displayThumb) {
-                    if (this.isVertical) {
-                        this._tempMeasure.height += this._effectiveThumbThickness;
-                    } else {
-                        this._tempMeasure.width += this._effectiveThumbThickness;
-                    }
-                }
-                this._backgroundImage._draw(this._tempMeasure, context);
-            }
 
-            // Bar
-            if (this._valueBarImage) {
+        this._prepareRenderingData("rectangle");
+        const thumbPosition = this._getThumbPosition();
+        var left = this._renderLeft;
+        var top = this._renderTop;
+        var width = this._renderWidth;
+        var height = this._renderHeight;
+
+        // Background
+        if (this._backgroundImage) {
+            this._tempMeasure.copyFromFloats(left, top, width, height);
+            if (this.isThumbClamped && this.displayThumb) {
                 if (this.isVertical) {
-                    if (this.isThumbClamped && this.displayThumb) {
-                        this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
-                    } else {
-                        this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition);
-                    }
+                    this._tempMeasure.height += this._effectiveThumbThickness;
                 } else {
-                    if (this.isThumbClamped && this.displayThumb) {
-                        this._tempMeasure.copyFromFloats(left, top, thumbPosition + this._effectiveThumbThickness / 2, height);
-                    }
-                    else {
-                        this._tempMeasure.copyFromFloats(left, top, thumbPosition, height);
-                    }
+                    this._tempMeasure.width += this._effectiveThumbThickness;
                 }
-                this._valueBarImage._draw(this._tempMeasure, context);
             }
+            this._backgroundImage._currentMeasure.copyFrom(this._tempMeasure);
+            this._backgroundImage._draw(context);
+        }
 
-            // Thumb
-            if (this.displayThumb) {
-                if (this.isVertical) {
-                    this._tempMeasure.copyFromFloats(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
+        // Bar
+        if (this._valueBarImage) {
+            if (this.isVertical) {
+                if (this.isThumbClamped && this.displayThumb) {
+                    this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
                 } else {
-                    this._tempMeasure.copyFromFloats(this._currentMeasure.left + thumbPosition, this._currentMeasure.top, this._effectiveThumbThickness, this._currentMeasure.height);
+                    this._tempMeasure.copyFromFloats(left, top + thumbPosition, width, height - thumbPosition);
                 }
-                this._thumbImage._draw(this._tempMeasure, context);
+            } else {
+                if (this.isThumbClamped && this.displayThumb) {
+                    this._tempMeasure.copyFromFloats(left, top, thumbPosition + this._effectiveThumbThickness / 2, height);
+                }
+                else {
+                    this._tempMeasure.copyFromFloats(left, top, thumbPosition, height);
+                }
+            }
+            this._valueBarImage._currentMeasure.copyFrom(this._tempMeasure);
+            this._valueBarImage._draw(context);
+        }
+
+        // Thumb
+        if (this.displayThumb) {
+            if (this.isVertical) {
+                this._tempMeasure.copyFromFloats(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
+            } else {
+                this._tempMeasure.copyFromFloats(this._currentMeasure.left + thumbPosition, this._currentMeasure.top, this._effectiveThumbThickness, this._currentMeasure.height);
             }
+
+            this._thumbImage._currentMeasure.copyFrom(this._tempMeasure);
+            this._thumbImage._draw(context);
         }
 
         context.restore();

+ 155 - 0
gui/src/2D/controls/sliders/scrollBar.ts

@@ -0,0 +1,155 @@
+import { BaseSlider } from "./baseSlider";
+import { Control } from "..";
+import { Vector2 } from "babylonjs";
+import { Measure } from "../../measure";
+
+/**
+ * Class used to create slider controls
+ */
+export class ScrollBar extends BaseSlider {
+    private _background = "black";
+    private _borderColor = "white";
+    private _thumbMeasure = new Measure(0, 0, 0, 0);
+
+    /** Gets or sets border color */
+    public get borderColor(): string {
+        return this._borderColor;
+    }
+
+    public set borderColor(value: string) {
+        if (this._borderColor === value) {
+            return;
+        }
+
+        this._borderColor = value;
+        this._markAsDirty();
+    }
+
+    /** Gets or sets background color */
+    public get background(): string {
+        return this._background;
+    }
+
+    public set background(value: string) {
+        if (this._background === value) {
+            return;
+        }
+
+        this._background = value;
+        this._markAsDirty();
+    }
+
+    /**
+     * Creates a new Slider
+     * @param name defines the control name
+     */
+    constructor(public name?: string) {
+        super(name);
+    }
+
+    protected _getTypeName(): string {
+        return "Scrollbar";
+    }
+
+    protected _getThumbThickness(): number {
+        var thumbThickness = 0;
+        if (this._thumbWidth.isPixel) {
+            thumbThickness = this._thumbWidth.getValue(this._host);
+        }
+        else {
+            thumbThickness = this._backgroundBoxThickness * this._thumbWidth.getValue(this._host);
+        }
+        return thumbThickness;
+    }
+
+    public _draw(context: CanvasRenderingContext2D): void {
+        context.save();
+
+        this._applyStates(context);
+        this._prepareRenderingData("rectangle");
+        var left = this._renderLeft;
+        var top = this._renderTop;
+        var width = this._renderWidth;
+        var height = this._renderHeight;
+
+        const thumbPosition = this._getThumbPosition();
+        context.fillStyle = this._background;
+
+        if (this.isVertical) {
+            context.fillRect(left, top, width, height + this._effectiveThumbThickness);
+        }
+        else {
+            context.fillRect(left, top, width + this._effectiveThumbThickness, height);
+        }
+
+        // Value bar
+        context.fillStyle = this.color;
+
+        // Thumb
+        if (this.isVertical) {
+            this._thumbMeasure.left = left - this._effectiveBarOffset;
+            this._thumbMeasure.top = this._currentMeasure.top + thumbPosition;
+            this._thumbMeasure.width = this._currentMeasure.width;
+            this._thumbMeasure.height = this._effectiveThumbThickness;
+        }
+        else {
+            this._thumbMeasure.left = this._currentMeasure.left + thumbPosition;
+            this._thumbMeasure.top = this._currentMeasure.top;
+            this._thumbMeasure.width = this._effectiveThumbThickness;
+            this._thumbMeasure.height = this._currentMeasure.height;
+        }
+
+        context.fillRect(this._thumbMeasure.left, this._thumbMeasure.top, this._thumbMeasure.width , this._thumbMeasure.height);
+
+        context.restore();
+    }
+
+    private _first: boolean;
+    private _originX: number;
+    private _originY: number;
+
+    /** @hidden */
+    protected _updateValueFromPointer(x: number, y: number): void {
+        if (this.rotation != 0) {
+            this._invertTransformMatrix.transformCoordinates(x, y, this._transformedPosition);
+            x = this._transformedPosition.x;
+            y = this._transformedPosition.y;
+        }
+
+        if (this._first) {
+            this._first = false;
+            this._originX = x;
+            this._originY = y;
+
+            // Check if move is required
+            if (x < this._thumbMeasure.left || x > this._thumbMeasure.left + this._thumbMeasure.width || y < this._thumbMeasure.top || y > this._thumbMeasure.top + this._thumbMeasure.height) {
+                if (this.isVertical) {
+                    this.value = this.minimum + (1 - ((y - this._currentMeasure.top) / this._currentMeasure.height)) * (this.maximum - this.minimum);
+                }
+                else {
+                    this.value = this.minimum + ((x - this._currentMeasure.left) / this._currentMeasure.width) * (this.maximum - this.minimum);
+                }
+            }
+        }
+
+        // Delta mode
+        let delta = 0;
+        if (this.isVertical) {
+            delta = -((y - this._originY) / (this._currentMeasure.height - this._effectiveThumbThickness));
+        }
+        else {
+            delta = (x - this._originX) / (this._currentMeasure.width - this._effectiveThumbThickness);
+        }
+
+        this.value += delta * (this.maximum - this.minimum);
+
+        this._originX = x;
+        this._originY = y;
+    }
+
+    public _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean {
+        this._first = true;
+
+        return super._onPointerDown(target, coordinates, pointerId, buttonIndex);
+    }
+}

+ 240 - 0
gui/src/2D/controls/sliders/slider.ts

@@ -0,0 +1,240 @@
+import { BaseSlider } from "./baseSlider";
+
+/**
+ * Class used to create slider controls
+ */
+export class Slider extends BaseSlider {
+    private _background = "black";
+    private _borderColor = "white";
+    private _isThumbCircle = false;
+    protected _displayValueBar = true;
+
+    /** Gets or sets a boolean indicating if the value bar must be rendered */
+    public get displayValueBar(): boolean {
+        return this._displayValueBar;
+    }
+
+    public set displayValueBar(value: boolean) {
+        if (this._displayValueBar === value) {
+            return;
+        }
+
+        this._displayValueBar = value;
+        this._markAsDirty();
+    }
+
+    /** Gets or sets border color */
+    public get borderColor(): string {
+        return this._borderColor;
+    }
+
+    public set borderColor(value: string) {
+        if (this._borderColor === value) {
+            return;
+        }
+
+        this._borderColor = value;
+        this._markAsDirty();
+    }
+
+    /** Gets or sets background color */
+    public get background(): string {
+        return this._background;
+    }
+
+    public set background(value: string) {
+        if (this._background === value) {
+            return;
+        }
+
+        this._background = value;
+        this._markAsDirty();
+    }
+
+    /** Gets or sets a boolean indicating if the thumb should be round or square */
+    public get isThumbCircle(): boolean {
+        return this._isThumbCircle;
+    }
+
+    public set isThumbCircle(value: boolean) {
+        if (this._isThumbCircle === value) {
+            return;
+        }
+
+        this._isThumbCircle = value;
+        this._markAsDirty();
+    }
+
+    /**
+     * Creates a new Slider
+     * @param name defines the control name
+     */
+    constructor(public name?: string) {
+        super(name);
+    }
+
+    protected _getTypeName(): string {
+        return "Slider";
+    }
+
+    public _draw(context: CanvasRenderingContext2D): void {
+        context.save();
+
+        this._applyStates(context);
+        this._prepareRenderingData(this.isThumbCircle ? "circle" : "rectangle");
+        var left = this._renderLeft;
+        var top = this._renderTop;
+        var width = this._renderWidth;
+        var height = this._renderHeight;
+
+        var radius = 0;
+
+        if (this.isThumbClamped && this.isThumbCircle) {
+            if (this.isVertical) {
+                top += (this._effectiveThumbThickness / 2);
+            }
+            else {
+                left += (this._effectiveThumbThickness / 2);
+            }
+
+            radius = this._backgroundBoxThickness / 2;
+        }
+        else {
+            radius = (this._effectiveThumbThickness - this._effectiveBarOffset) / 2;
+        }
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowColor = this.shadowColor;
+            context.shadowBlur = this.shadowBlur;
+            context.shadowOffsetX = this.shadowOffsetX;
+            context.shadowOffsetY = this.shadowOffsetY;
+        }
+
+        const thumbPosition = this._getThumbPosition();
+        context.fillStyle = this._background;
+
+        if (this.isVertical) {
+            if (this.isThumbClamped) {
+                if (this.isThumbCircle) {
+                    context.beginPath();
+                    context.arc(left + this._backgroundBoxThickness / 2, top, radius, Math.PI, 2 * Math.PI);
+                    context.fill();
+                    context.fillRect(left, top, width, height);
+                }
+                else {
+                    context.fillRect(left, top, width, height + this._effectiveThumbThickness);
+                }
+            }
+            else {
+                context.fillRect(left, top, width, height);
+            }
+        }
+        else {
+            if (this.isThumbClamped) {
+                if (this.isThumbCircle) {
+                    context.beginPath();
+                    context.arc(left + this._backgroundBoxLength, top + (this._backgroundBoxThickness / 2), radius, 0, 2 * Math.PI);
+                    context.fill();
+                    context.fillRect(left, top, width, height);
+                }
+                else {
+                    context.fillRect(left, top, width + this._effectiveThumbThickness, height);
+                }
+            }
+            else {
+                context.fillRect(left, top, width, height);
+            }
+        }
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            context.shadowBlur = 0;
+            context.shadowOffsetX = 0;
+            context.shadowOffsetY = 0;
+        }
+
+        // Value bar
+        context.fillStyle = this.color;
+        if (this._displayValueBar) {
+            if (this.isVertical) {
+                if (this.isThumbClamped) {
+                    if (this.isThumbCircle) {
+                        context.beginPath();
+                        context.arc(left + this._backgroundBoxThickness / 2, top + this._backgroundBoxLength, radius, 0, 2 * Math.PI);
+                        context.fill();
+                        context.fillRect(left, top + thumbPosition, width, height - thumbPosition);
+                    }
+                    else {
+                        context.fillRect(left, top + thumbPosition, width, height - thumbPosition + this._effectiveThumbThickness);
+                    }
+                }
+                else {
+                    context.fillRect(left, top + thumbPosition, width, height - thumbPosition);
+                }
+            }
+            else {
+                if (this.isThumbClamped) {
+                    if (this.isThumbCircle) {
+                        context.beginPath();
+                        context.arc(left, top + this._backgroundBoxThickness / 2, radius, 0, 2 * Math.PI);
+                        context.fill();
+                        context.fillRect(left, top, thumbPosition, height);
+                    }
+                    else {
+                        context.fillRect(left, top, thumbPosition, height);
+                    }
+                }
+                else {
+                    context.fillRect(left, top, thumbPosition, height);
+                }
+            }
+        }
+
+        // Thumb
+        if (this.displayThumb) {
+            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+                context.shadowColor = this.shadowColor;
+                context.shadowBlur = this.shadowBlur;
+                context.shadowOffsetX = this.shadowOffsetX;
+                context.shadowOffsetY = this.shadowOffsetY;
+            }
+            if (this._isThumbCircle) {
+                context.beginPath();
+                if (this.isVertical) {
+                    context.arc(left + this._backgroundBoxThickness / 2, top + thumbPosition, radius, 0, 2 * Math.PI);
+                }
+                else {
+                    context.arc(left + thumbPosition, top + (this._backgroundBoxThickness / 2), radius, 0, 2 * Math.PI);
+                }
+                context.fill();
+                if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+                    context.shadowBlur = 0;
+                    context.shadowOffsetX = 0;
+                    context.shadowOffsetY = 0;
+                }
+                context.strokeStyle = this._borderColor;
+                context.stroke();
+            }
+            else {
+                if (this.isVertical) {
+                    context.fillRect(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
+                }
+                else {
+                    context.fillRect(this._currentMeasure.left + thumbPosition, this._currentMeasure.top, this._effectiveThumbThickness, this._currentMeasure.height);
+                }
+                if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+                    context.shadowBlur = 0;
+                    context.shadowOffsetX = 0;
+                    context.shadowOffsetY = 0;
+                }
+                context.strokeStyle = this._borderColor;
+                if (this.isVertical) {
+                    context.strokeRect(left - this._effectiveBarOffset, this._currentMeasure.top + thumbPosition, this._currentMeasure.width, this._effectiveThumbThickness);
+                }
+                else {
+                    context.strokeRect(this._currentMeasure.left + thumbPosition, this._currentMeasure.top, this._effectiveThumbThickness, this._currentMeasure.height);
+                }
+            }
+        }
+        context.restore();
+    }
+}

+ 41 - 22
gui/src/2D/controls/stackPanel.ts

@@ -10,7 +10,6 @@ export class StackPanel extends Container {
     private _manualWidth = false;
     private _manualHeight = false;
     private _doNotTrackManualChanges = false;
-    private _tempMeasureStore = Measure.Empty();
 
     /** Gets or sets a boolean indicating if the stack panel is vertical or horizontal*/
     public get isVertical(): boolean {
@@ -82,13 +81,41 @@ export class StackPanel extends Container {
         return "StackPanel";
     }
 
+    /** @hidden */
     protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        for (var child of this._children) {
+            if (this._isVertical) {
+                child.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
+            } else {
+                child.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
+            }
+        }
+
+        super._preMeasure(parentMeasure, context);
+    }
+
+    protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        super._additionalProcessing(parentMeasure, context);
+
+        this._measureForChildren.copyFrom(parentMeasure);
+
+        this._measureForChildren.left = this._currentMeasure.left;
+        this._measureForChildren.top = this._currentMeasure.top;
+
+        if (this.isVertical || this._manualWidth) {
+            this._measureForChildren.width = this._currentMeasure.width;
+        } else if (!this.isVertical || this._manualHeight) {
+            this._measureForChildren.height = this._currentMeasure.height;
+        }
+    }
+
+    protected _postMeasure(): void {
         var stackWidth = 0;
         var stackHeight = 0;
         for (var child of this._children) {
-            this._tempMeasureStore.copyFrom(child._currentMeasure);
-            child._currentMeasure.copyFrom(parentMeasure);
-            child._measure();
+            if (!child.isVisible || child.notRenderable) {
+                continue;
+            }
 
             if (this._isVertical) {
                 child.top = stackHeight + "px";
@@ -96,25 +123,21 @@ export class StackPanel extends Container {
                     child._markAsDirty();
                 }
                 child._top.ignoreAdaptiveScaling = true;
-                stackHeight += child._currentMeasure.height;
+                stackHeight += child._currentMeasure.height + child.paddingTopInPixels;
                 if (child._currentMeasure.width > stackWidth) {
                     stackWidth = child._currentMeasure.width;
                 }
-                child.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
             } else {
                 child.left = stackWidth + "px";
                 if (!child._left.ignoreAdaptiveScaling) {
                     child._markAsDirty();
                 }
                 child._left.ignoreAdaptiveScaling = true;
-                stackWidth += child._currentMeasure.width;
+                stackWidth += child._currentMeasure.width + child.paddingLeftInPixels;
                 if (child._currentMeasure.height > stackHeight) {
                     stackHeight = child._currentMeasure.height;
                 }
-                child.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
             }
-
-            child._currentMeasure.copyFrom(this._tempMeasureStore);
         }
 
         this._doNotTrackManualChanges = true;
@@ -125,21 +148,17 @@ export class StackPanel extends Container {
         let panelWidthChanged = false;
         let panelHeightChanged = false;
 
-        let previousHeight = this.height;
-        let previousWidth = this.width;
-
-        if (!this._manualHeight) {
-            // do not specify height if strictly defined by user
+        if (!this._manualHeight) { // do not specify height if strictly defined by user
+            let previousHeight = this.height;
             this.height = stackHeight + "px";
+            panelHeightChanged = previousHeight !== this.height || !this._height.ignoreAdaptiveScaling;
         }
-        if (!this._manualWidth) {
-            // do not specify width if strictly defined by user
+        if (!this._manualWidth) { // do not specify width if strictly defined by user
+            let previousWidth = this.width;
             this.width = stackWidth + "px";
+            panelWidthChanged = previousWidth !== this.width || !this._width.ignoreAdaptiveScaling;
         }
 
-        panelWidthChanged = previousWidth !== this.width || !this._width.ignoreAdaptiveScaling;
-        panelHeightChanged = previousHeight !== this.height || !this._height.ignoreAdaptiveScaling;
-
         if (panelHeightChanged) {
             this._height.ignoreAdaptiveScaling = true;
         }
@@ -151,9 +170,9 @@ export class StackPanel extends Container {
         this._doNotTrackManualChanges = false;
 
         if (panelWidthChanged || panelHeightChanged) {
-            this._markAllAsDirty();
+            this._rebuildLayout = true;
         }
 
-        super._preMeasure(parentMeasure, context);
+        super._postMeasure();
     }
 }

+ 49 - 25
gui/src/2D/controls/textBlock.ts

@@ -65,12 +65,17 @@ export class TextBlock extends Control {
      * Gets or sets an boolean indicating that the TextBlock will be resized to fit container
      */
     public set resizeToFit(value: boolean) {
+        if (this._resizeToFit === value) {
+            return;
+        }
         this._resizeToFit = value;
 
         if (this._resizeToFit) {
             this._width.ignoreAdaptiveScaling = true;
             this._height.ignoreAdaptiveScaling = true;
         }
+
+        this._markAsDirty();
     }
 
     /**
@@ -221,6 +226,44 @@ export class TextBlock extends Control {
         return "TextBlock";
     }
 
+    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        if (!this._fontOffset) {
+            this._fontOffset = Control._GetFontOffset(context.font);
+        }
+
+        // Prepare lines
+        this._lines = this._breakLines(this._currentMeasure.width, context);
+        this.onLinesReadyObservable.notifyObservers(this);
+
+        let maxLineWidth: number = 0;
+
+        for (let i = 0; i < this._lines.length; i++) {
+            const line = this._lines[i];
+
+            if (line.width > maxLineWidth) {
+                maxLineWidth = line.width;
+            }
+        }
+
+        if (this._resizeToFit) {
+            if (this._textWrapping === TextWrapping.Clip) {
+                let newWidth = this.paddingLeftInPixels + this.paddingRightInPixels + maxLineWidth;
+                if (newWidth !== this._width.internalValue) {
+                    this._width.updateInPlace(newWidth, ValueAndUnit.UNITMODE_PIXEL);
+                    this._isDirty = true;
+                }
+            }
+            let newHeight = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length;
+
+            if (newHeight !== this._height.internalValue) {
+                this._height.updateInPlace(newHeight, ValueAndUnit.UNITMODE_PIXEL);
+                this._isDirty = true;
+            }
+        }
+
+        super._processMeasures(parentMeasure, context);
+    }
+
     private _drawText(text: string, textWidth: number, y: number, context: CanvasRenderingContext2D): void {
         var width = this._currentMeasure.width;
         var x = 0;
@@ -250,15 +293,14 @@ export class TextBlock extends Control {
     }
 
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
 
         this._applyStates(context);
 
-        if (this._processMeasures(parentMeasure, context)) {
-            // Render lines
-            this._renderLines(context);
-        }
+        // Render lines
+        this._renderLines(context);
+
         context.restore();
     }
 
@@ -270,20 +312,15 @@ export class TextBlock extends Control {
         }
     }
 
-    protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
-        this._lines = this._breakLines(this._currentMeasure.width, context);
-        this.onLinesReadyObservable.notifyObservers(this);
-    }
-
     protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[] {
         var lines = [];
         var _lines = this.text.split("\n");
 
-        if (this._textWrapping === TextWrapping.Ellipsis && !this._resizeToFit) {
+        if (this._textWrapping === TextWrapping.Ellipsis) {
             for (var _line of _lines) {
                 lines.push(this._parseLineEllipsis(_line, refWidth, context));
             }
-        } else if (this._textWrapping === TextWrapping.WordWrap && !this._resizeToFit) {
+        } else if (this._textWrapping === TextWrapping.WordWrap) {
             for (var _line of _lines) {
                 lines.push(...this._parseLineWordWrap(_line, refWidth, context));
             }
@@ -342,10 +379,6 @@ export class TextBlock extends Control {
 
     protected _renderLines(context: CanvasRenderingContext2D): void {
         var height = this._currentMeasure.height;
-
-        if (!this._fontOffset) {
-            this._fontOffset = Control._GetFontOffset(context.font);
-        }
         var rootY = 0;
         switch (this._textVerticalAlignment) {
             case Control.VERTICAL_ALIGNMENT_TOP:
@@ -361,8 +394,6 @@ export class TextBlock extends Control {
 
         rootY += this._currentMeasure.top;
 
-        var maxLineWidth: number = 0;
-
         for (let i = 0; i < this._lines.length; i++) {
             const line = this._lines[i];
 
@@ -377,13 +408,6 @@ export class TextBlock extends Control {
 
             this._drawText(line.text, line.width, rootY, context);
             rootY += this._fontOffset.height;
-
-            if (line.width > maxLineWidth) { maxLineWidth = line.width; }
-        }
-
-        if (this._resizeToFit) {
-            this.width = this.paddingLeftInPixels + this.paddingRightInPixels + maxLineWidth + 'px';
-            this.height = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length + 'px';
         }
     }
 

+ 13 - 0
gui/src/2D/valueAndUnit.ts

@@ -57,6 +57,19 @@ export class ValueAndUnit {
     }
 
     /**
+     * Update the current value and unit. This should be done cautiously as the GUi won't be marked as dirty with this function.
+     * @param value defines the value to store
+     * @param unit defines the unit to store
+     * @returns the current ValueAndUnit
+     */
+    public updateInPlace(value: number, unit = ValueAndUnit.UNITMODE_PIXEL): ValueAndUnit {
+        this._value = value;
+        this.unit = unit;
+
+        return this;
+    }
+
+    /**
      * Gets the value accordingly to its unit
      * @param host  defines the root host
      * @returns the value

+ 9 - 1
inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx

@@ -19,7 +19,7 @@ import { TextBlockPropertyGridComponent } from "./propertyGrids/gui/textBlockPro
 import { TextBlock } from "babylonjs-gui/2D/controls/textBlock";
 import { InputText } from "babylonjs-gui/2D/controls/inputText";
 import { InputTextPropertyGridComponent } from "./propertyGrids/gui/inputTextPropertyGridComponent";
-import { ColorPicker, Image, Slider, ImageBasedSlider, Rectangle, Ellipse, Checkbox, RadioButton, Line } from "babylonjs-gui";
+import { ColorPicker, Image, Slider, ImageBasedSlider, Rectangle, Ellipse, Checkbox, RadioButton, Line, ScrollViewer } from "babylonjs-gui";
 import { ColorPickerPropertyGridComponent } from "./propertyGrids/gui/colorPickerPropertyGridComponent";
 import { AnimationGroupGridComponent } from "./propertyGrids/animationGroupPropertyGridComponent";
 import { LockObject } from "./propertyGrids/lockObject";
@@ -31,6 +31,7 @@ import { EllipsePropertyGridComponent } from "./propertyGrids/gui/ellipsePropert
 import { CheckboxPropertyGridComponent } from "./propertyGrids/gui/checkboxPropertyGridComponent";
 import { RadioButtonPropertyGridComponent } from "./propertyGrids/gui/radioButtonPropertyGridComponent";
 import { LinePropertyGridComponent } from "./propertyGrids/gui/linePropertyGridComponent";
+import { ScrollViewerPropertyGridComponent } from "./propertyGrids/gui/scrollViewerPropertyGridComponent";
 
 export class PropertyGridTabComponent extends PaneComponent {
     private _timerIntervalId: number;
@@ -214,6 +215,13 @@ export class PropertyGridTabComponent extends PaneComponent {
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
             }
 
+            if (className === "ScrollViewer") {
+                const scrollViewer = entity as ScrollViewer;
+                return (<ScrollViewerPropertyGridComponent scrollViewer={scrollViewer}
+                    lockObject={this._lockObject}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }            
+
             if (className === "Ellipse") {
                 const ellipse = entity as Ellipse;
                 return (<EllipsePropertyGridComponent ellipse={ellipse}

+ 1 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx

@@ -42,7 +42,7 @@ export class CommonControlPropertyGridComponent extends React.Component<ICommonC
                     <TextLineComponent label="Class" value={control.getClassName()} />
                     <SliderLineComponent label="Alpha" target={control} propertyName="alpha" minimum={0} maximum={1} step={0.01} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     {
-                        control.color &&
+                        (control as any).color !== undefined &&
                         <TextInputLineComponent lockObject={this.props.lockObject} label="Color" target={control} propertyName="color" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     }
                     {

+ 42 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/gui/scrollViewerPropertyGridComponent.tsx

@@ -0,0 +1,42 @@
+import * as React from "react";
+import { Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonControlPropertyGridComponent } from "./commonControlPropertyGridComponent";
+import { LockObject } from "../lockObject";
+import { ScrollViewer } from "babylonjs-gui";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
+import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
+
+interface IScrollViewerPropertyGridComponentProps {
+    scrollViewer: ScrollViewer,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class ScrollViewerPropertyGridComponent extends React.Component<IScrollViewerPropertyGridComponentProps> {
+    constructor(props: IScrollViewerPropertyGridComponentProps) {
+        super(props);
+    }
+
+    render() {
+        const scrollViewer = this.props.scrollViewer;
+
+        return (
+            <div className="pane">
+                <CommonControlPropertyGridComponent lockObject={this.props.lockObject} control={scrollViewer} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="RECTANGLE">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Thickness" target={scrollViewer} propertyName="thickness" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Corner radius" target={scrollViewer} propertyName="cornerRadius" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="SCROLLVIEWER">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Bar size" target={scrollViewer} propertyName="barSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Bar color" target={scrollViewer} propertyName="barColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Bar border color" target={scrollViewer} propertyName="barBorderColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Bar background" target={scrollViewer} propertyName="barBackground" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Wheel precision" target={scrollViewer} propertyName="wheelPrecision" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 1 - 0
inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx

@@ -100,6 +100,7 @@ export class ToolsTabComponent extends PaneComponent {
             })
             .catch((error: any) => {
                 console.error(error);
+                alert(error);
             });
     }
 

+ 5 - 0
inspector/src/components/sceneExplorer/entities/gui/advancedDynamicTextureTreeItemComponent.tsx

@@ -44,6 +44,11 @@ export class AdvancedDynamicTextureTreeItemComponent extends React.Component<IAd
                 if (!this.props.onSelectionChangedObservable) {
                     return;
                 }
+
+                if (control.getClassName() === "ScrollViewerWindow") {
+                    control = control.getAscendantOfClass("ScrollViewer")!;
+                }
+
                 this.props.onSelectionChangedObservable.notifyObservers(control);
             });
         }

+ 22 - 0
src/Cameras/arcRotateCamera.ts

@@ -562,6 +562,8 @@ import { ArcRotateCameraInputsManager } from "Cameras/arcRotateCameraInputsManag
         protected _targetBoundingCenter: Nullable<Vector3>;
 
         private _computationVector: Vector3 = Vector3.Zero();
+        private _tempAxisVector: Vector3;
+        private _tempAxisRotationMatrix: Matrix;
 
         /**
          * Instantiates a new ArcRotateCamera in a given scene
@@ -925,6 +927,26 @@ import { ArcRotateCameraInputsManager } from "Cameras/arcRotateCameraInputsManag
 
             var target = this._getTargetPosition();
             this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
+
+            // Rotate according to up vector
+            if (this.upVector.x !== 0 || this.upVector.y !== 1.0 || this.upVector.z !== 0) {
+
+                if (!this._tempAxisVector) {
+                    this._tempAxisVector = new Vector3();
+                    this._tempAxisRotationMatrix = new Matrix();
+                }
+
+                Vector3.CrossToRef(Vector3.Up(), this.upVector, this._tempAxisVector);
+                this._tempAxisVector.normalize();
+
+                let angle = Math.acos(Vector3.Dot(Vector3.UpReadOnly, this.upVector));
+
+                Matrix.RotationAxisToRef(this._tempAxisVector, angle, this._tempAxisRotationMatrix);
+
+                this._tempAxisVector.copyFrom(this._computationVector);
+                Vector3.TransformCoordinatesToRef(this._tempAxisVector, this._tempAxisRotationMatrix, this._computationVector);
+            }
+
             target.addToRef(this._computationVector, this._newPosition);
             if (this.getScene().collisionsEnabled && this.checkCollisions) {
                 if (!this._collider) {

+ 20 - 0
src/Maths/math.ts

@@ -1542,6 +1542,8 @@ import { Scalar } from "./math.scalar";
      * Reminder: js uses a left handed forward facing system
      */
     export class Vector3 {
+        private static _UpReadOnly = Vector3.Up() as DeepImmutable<Vector3>;
+
         /**
          * Creates a new Vector3 object from the given x, y, z (floats) coordinates.
          * @param x defines the first coordinates (on X axis)
@@ -2226,6 +2228,14 @@ import { Scalar } from "./math.scalar";
         public static Up(): Vector3 {
             return new Vector3(0.0, 1.0, 0.0);
         }
+
+        /**
+         * Gets a up Vector3 that must not be updated
+         */
+        public static get UpReadOnly(): DeepImmutable<Vector3> {
+            return Vector3._UpReadOnly;
+        }
+
         /**
          * Returns a new Vector3 set to (0.0, -1.0, 0.0)
          * @returns a new down Vector3
@@ -3401,6 +3411,16 @@ import { Scalar } from "./math.scalar";
             result.z = (x * m[2]) + (y * m[6]) + (z * m[10]);
             result.w = w;
         }
+
+        /**
+         * Creates a new Vector4 from a Vector3
+         * @param source defines the source data
+         * @param w defines the 4th component (default is 0)
+         * @returns a new Vector4
+         */
+        public static FromVector3(source: Vector3, w: number = 0) {
+            return new Vector4(source.x, source.y, source.z, w);
+        }
     }
 
     /**