Преглед на файлове

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

André Reschke преди 6 години
родител
ревизия
e276e19132
променени са 100 файла, в които са добавени 36677 реда и са изтрити 30757 реда
  1. 2 2
      .github/ISSUE_TEMPLATE.md
  2. 1 1
      .vscode/tasks.json
  3. 15053 14592
      Playground/babylon.d.txt
  4. 3 2
      Playground/debug.html
  5. 1 0
      Playground/frame.html
  6. 1 0
      Playground/full.html
  7. 4 2
      Playground/index-local.html
  8. 3 2
      Playground/index.html
  9. 3 2
      Playground/indexStable.html
  10. BIN
      Playground/textures/panel_blue2x.9.direct.png
  11. BIN
      Playground/textures/panel_blue2x.9.inv.png
  12. BIN
      Playground/textures/panel_blue2x.9.png
  13. 2 1
      Playground/ts.html
  14. 1 0
      Playground/zipContent/index.html
  15. 1 0
      Tools/Gulp/config.json
  16. 6 3
      Tools/Gulp/gulpfile.js
  17. 7 7
      Tools/Gulp/package.json
  18. 2 1
      Viewer/tests/validation/validate.html
  19. 1 1
      contributing.md
  20. 666 0
      dist/preview release/ammo.js
  21. 14263 13951
      dist/preview release/babylon.d.ts
  22. 1 1
      dist/preview release/babylon.js
  23. 934 93
      dist/preview release/babylon.max.js
  24. 934 93
      dist/preview release/babylon.no-module.max.js
  25. 1 1
      dist/preview release/babylon.worker.js
  26. 936 95
      dist/preview release/es6.js
  27. 1 1
      dist/preview release/glTF2Interface/package.json
  28. 239 73
      dist/preview release/gui/babylon.gui.d.ts
  29. 1 1
      dist/preview release/gui/babylon.gui.js
  30. 1 1
      dist/preview release/gui/babylon.gui.min.js
  31. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  32. 502 164
      dist/preview release/gui/babylon.gui.module.d.ts
  33. 2 2
      dist/preview release/gui/package.json
  34. 8 8
      dist/preview release/inspector/babylon.inspector.bundle.js
  35. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  36. 5 5
      dist/preview release/inspector/package.json
  37. 1 1
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  38. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  39. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  40. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  41. 3 3
      dist/preview release/loaders/package.json
  42. 2 2
      dist/preview release/materialsLibrary/package.json
  43. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  44. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  45. 1 1
      dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js
  46. 2 2
      dist/preview release/postProcessesLibrary/package.json
  47. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  48. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  49. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  50. 3 3
      dist/preview release/serializers/package.json
  51. 15 1
      dist/preview release/viewer/babylon.viewer.d.ts
  52. 3 3
      dist/preview release/viewer/babylon.viewer.js
  53. 5 5
      dist/preview release/viewer/babylon.viewer.max.js
  54. 18 1
      dist/preview release/viewer/babylon.viewer.module.d.ts
  55. 23 3
      dist/preview release/what's new.md
  56. 122 0
      gui/src/2D/adtInstrumentation.ts
  57. 37 11
      gui/src/2D/advancedDynamicTexture.ts
  58. 6 1
      gui/src/2D/controls/button.ts
  59. 32 34
      gui/src/2D/controls/checkbox.ts
  60. 487 439
      gui/src/2D/controls/colorpicker.ts
  61. 85 53
      gui/src/2D/controls/container.ts
  62. 185 72
      gui/src/2D/controls/control.ts
  63. 2 3
      gui/src/2D/controls/displayGrid.ts
  64. 46 5
      gui/src/2D/controls/grid.ts
  65. 257 35
      gui/src/2D/controls/image.ts
  66. 4 4
      gui/src/2D/controls/index.ts
  67. 330 143
      gui/src/2D/controls/inputText.ts
  68. 9 11
      gui/src/2D/controls/line.ts
  69. 19 21
      gui/src/2D/controls/multiLine.ts
  70. 31 35
      gui/src/2D/controls/radioButton.ts
  71. 0 364
      gui/src/2D/controls/scrollViewer.ts
  72. 386 0
      gui/src/2D/controls/scrollViewers/scrollViewer.ts
  73. 74 0
      gui/src/2D/controls/scrollViewers/scrollViewerWindow.ts
  74. 1 1
      gui/src/2D/controls/selector.ts
  75. 0 227
      gui/src/2D/controls/slider.ts
  76. 4 3
      gui/src/2D/controls/baseSlider.ts
  77. 46 44
      gui/src/2D/controls/imageBasedSlider.ts
  78. 147 0
      gui/src/2D/controls/sliders/scrollBar.ts
  79. 240 0
      gui/src/2D/controls/sliders/slider.ts
  80. 51 30
      gui/src/2D/controls/stackPanel.ts
  81. 49 25
      gui/src/2D/controls/textBlock.ts
  82. 1 0
      gui/src/2D/index.ts
  83. 13 0
      gui/src/2D/valueAndUnit.ts
  84. 2 2
      gui/src/3D/controls/cylinderPanel.ts
  85. 2 2
      gui/src/3D/controls/planePanel.ts
  86. 2 2
      gui/src/3D/controls/scatterPanel.ts
  87. 2 2
      gui/src/3D/controls/spherePanel.ts
  88. 75 28
      inspector/src/components/actionTabs/actionTabs.scss
  89. 81 7
      inspector/src/components/actionTabs/lines/color3LineComponent.tsx
  90. 1 1
      inspector/src/components/actionTabs/lines/floatLineComponent.tsx
  91. 1 1
      inspector/src/components/actionTabs/lines/numericInputComponent.tsx
  92. 2 1
      inspector/src/components/actionTabs/lines/textLineComponent.tsx
  93. 2 2
      inspector/src/components/actionTabs/tabs/debugTabComponent.tsx
  94. 17 1
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  95. 2 4
      inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx
  96. 29 1
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx
  97. 80 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/gridPropertyGridComponent.tsx
  98. 2 1
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/imagePropertyGridComponent.tsx
  99. 41 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/scrollViewerPropertyGridComponent.tsx
  100. 0 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx

+ 2 - 2
.github/ISSUE_TEMPLATE.md

@@ -1,5 +1,5 @@
 Before creating an issue, please make sure to provide the following template (based on why you create the issue):
-(Please do not use Github issues for questions - We have a really active forum to help answering questions (http://www.html5gamedevs.com/forum/16-babylonjs/))
+(Please do not use Github issues for questions - We have a really active forum to help answering questions (https://forum.babylonjs.com/))
 
 # Bugs
 
@@ -9,7 +9,7 @@ Before creating an issue, please make sure to provide the following template (ba
 
 # Feature request
 
-- Link to [forum](http://www.html5gamedevs.com/forum/16-babylonjs/) discussion about the feature:
+- Link to [forum](https://forum.babylonjs.com/) discussion about the feature:
 - Feature description
 - Additional links that could help implementing the feature:
 

+ 1 - 1
.vscode/tasks.json

@@ -36,7 +36,7 @@
                 "background": {
                     "activeOnStart": true,
                     "beginsPattern": "Starting \\'watch\\'",
-                    "endsPattern": "Finished \\'run\\'"
+                    "endsPattern": "Entrypoint babylonjs-inspector"
                 }
             }
         },

Файловите разлики са ограничени, защото са твърде много
+ 15053 - 14592
Playground/babylon.d.txt


+ 3 - 2
Playground/debug.html

@@ -35,6 +35,7 @@
         <script src="js/libs/fileSaver.js"></script>
 
         <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>
@@ -450,7 +451,7 @@
                     <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
                 </div>
                 <div class='link'>
-                    <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+                    <a target='_new' href="https://forum.babylonjs.com/">Forum</a>
                 </div>
                 <div class='link'>
                     <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>
@@ -503,4 +504,4 @@
         </script>
     </body>
 
-</html>
+</html>

+ 1 - 0
Playground/frame.html

@@ -28,6 +28,7 @@
 
         <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
         <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/earcut.min.js"></script>

+ 1 - 0
Playground/full.html

@@ -26,6 +26,7 @@
 
         <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
         <!-- Babylon.js -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 4 - 2
Playground/index-local.html

@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 
+
 <head>
     <title>Babylon.js Playground</title>
     <meta charset='utf-8' />
@@ -14,6 +15,7 @@
     <script src="js/libs/jszip.min.js"></script>
     <script src="js/libs/fileSaver.js"></script>
     <!-- Dependencies -->
+    <script src="../dist/preview%20release/ammo.js"></script>
     <script src="../dist/preview%20release/cannon.js"></script>
     <script src="../dist/preview%20release/Oimo.js"></script>
     <script src="../dist/preview%20release/gltf_validator.js"></script>
@@ -389,7 +391,7 @@
                 <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
             </div>
             <div class='link'>
-                <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+                <a target='_new' href="https://forum.babylonjs.com/">Forum</a>
             </div>
             <div class='link'>
                 <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>
@@ -452,4 +454,4 @@
     </script>
 </body>
 
-</html>
+</html>

+ 3 - 2
Playground/index.html

@@ -34,6 +34,7 @@
         <script src="js/libs/jszip.min.js"></script>
         <script src="js/libs/fileSaver.js"></script>
         <!-- Dependencies -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>
@@ -424,7 +425,7 @@
                     <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
                 </div>
                 <div class='link'>
-                    <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+                    <a target='_new' href="https://forum.babylonjs.com/">Forum</a>
                 </div>
                 <div class='link'>
                     <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>
@@ -486,4 +487,4 @@
         </script>
     </body>
 
-</html>
+</html>

+ 3 - 2
Playground/indexStable.html

@@ -34,6 +34,7 @@
     <script src="js/libs/jszip.min.js"></script>
     <script src="js/libs/fileSaver.js"></script>
     <!-- Physics -->
+    <script src="https://cdn.babylonjs.com/ammo.js"></script>
     <script src="https://cdn.babylonjs.com/cannon.js"></script>
     <script src="https://cdn.babylonjs.com/Oimo.js"></script>
     <script src="https://cdn.babylonjs.com/gltf_validator.js"></script>
@@ -415,7 +416,7 @@
                 <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
             </div>
             <div class='link'>
-                <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+                <a target='_new' href="https://forum.babylonjs.com/">Forum</a>
             </div>
             <div class='link'>
                 <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>
@@ -472,4 +473,4 @@
     </script>
 </body>
 
-</html>
+</html>

BIN
Playground/textures/panel_blue2x.9.direct.png


BIN
Playground/textures/panel_blue2x.9.inv.png


BIN
Playground/textures/panel_blue2x.9.png


+ 2 - 1
Playground/ts.html

@@ -34,6 +34,7 @@
         <script src="js/libs/jszip.min.js"></script>
         <script src="js/libs/fileSaver.js"></script>
         <!-- Dependencies -->
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/earcut.min.js"></script>
@@ -417,7 +418,7 @@
                     <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
                 </div>
                 <div class='link'>
-                    <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+                    <a target='_new' href="https://forum.babylonjs.com/">Forum</a>
                 </div>
                 <div class='link'>
                     <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>

+ 1 - 0
Playground/zipContent/index.html

@@ -8,6 +8,7 @@
         <!-- Babylon.js -->
         <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
         <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
+        <script src="https://preview.babylonjs.com/ammo.js"></script>
         <script src="https://preview.babylonjs.com/cannon.js"></script>
         <script src="https://preview.babylonjs.com/Oimo.js"></script>
         <script src="https://preview.babylonjs.com/gltf_validator.js"></script>

+ 1 - 0
Tools/Gulp/config.json

@@ -1125,6 +1125,7 @@
                 "../../src/Physics/babylon.physicsEngine.js",
                 "../../src/Physics/babylon.physicsHelper.js",
                 "../../src/Physics/Plugins/babylon.cannonJSPlugin.js",
+                "../../src/Physics/Plugins/babylon.ammoJSPlugin.js",
                 "../../src/Physics/Plugins/babylon.oimoJSPlugin.js",
                 "../../src/Physics/babylon.physicsEngineComponent.js"
             ],

+ 6 - 3
Tools/Gulp/gulpfile.js

@@ -20,7 +20,7 @@ var replace = require("gulp-replace");
 var uncommentShader = require("./gulp-removeShaderComments");
 var expect = require("gulp-expect-file");
 var optimisejs = require("gulp-optimize-js");
-var webserver = require("gulp-webserver");
+var connect = require("gulp-connect");
 var path = require("path");
 const webpack = require('webpack');
 var webpackStream = require("webpack-stream");
@@ -847,16 +847,19 @@ gulp.task("deployLocalDev", function() {
  */
 gulp.task("webserver", function() {
     var options = {
+        root: "../../.",
         port: 1338,
         livereload: false,
-        middleware: [cors()]
+        middleware: function() {
+            return [cors()];
+        }
     };
 
     if (commandLineOptions.public) {
         options.host = "0.0.0.0";
     }
 
-    return gulp.src("../../.").pipe(webserver(options));
+    connect.server(options);
 });
 
 /**

+ 7 - 7
Tools/Gulp/package.json

@@ -12,10 +12,14 @@
         "@types/node": "^8.10.22",
         "chai": "^4.1.2",
         "color-support": "^1.1.3",
+        "cors": "^2.8.4",
         "del": "3.0.0",
+        "dts-bundle": "^0.7.3",
         "gulp": "^4.0.0",
+        "gulp-clean": "^0.4.0",
         "gulp-clean-ts-extends": "~0.1.1",
         "gulp-concat": "~2.6.1",
+        "gulp-connect": "^5.6.1",
         "gulp-content-to-variable": "^0.1.0",
         "gulp-debug": "^4.0.0",
         "gulp-expect-file": "^1.0.0",
@@ -25,12 +29,10 @@
         "gulp-replace": "~1.0.0",
         "gulp-sourcemaps": "~2.6.4",
         "gulp-tslint": "^8.1.3",
-        "gulp-typedoc": "^2.2.0",
+        "gulp-typedoc": "^2.2.1",
         "gulp-typescript": "4.0.2",
         "gulp-uglify": "^3.0.1",
-        "gulp-webserver": "^0.9.1",
-        "cors": "^2.8.4",
-        "karma": "^2.0.5",
+        "karma": "^3.1.1",
         "karma-browserstack-launcher": "^1.3.0",
         "karma-chai": "^0.1.0",
         "karma-chrome-launcher": "^2.2.0",
@@ -49,9 +51,7 @@
         "typedoc": "^0.12.0",
         "typescript": "~3.0.1",
         "webpack": "^4.16.3",
-        "webpack-stream": "5.0.0",
-        "dts-bundle": "^0.7.3",
-        "gulp-clean": "^0.4.0"
+        "webpack-stream": "5.0.0"
     },
     "scripts": {
         "install": "cd ../../gui && npm install && cd ../Tools/Gulp/ &&  cd ../../inspector && npm install && cd ../Tools/Gulp/ && npm --prefix ../../Playground/ install ../../Playground/ && npm --prefix ../../tests/unit/ install ../../tests/unit/ && npm --prefix ../../Viewer/tests/ install ../../Viewer/tests/ && cd ../../Viewer && npm install && cd ../Tools/Gulp/ && gulp deployLocalDev"

+ 2 - 1
Viewer/tests/validation/validate.html

@@ -3,7 +3,8 @@
 <head>
 	<title>BabylonJS - Build validation page</title>
 	<link href="index.css" rel="stylesheet" />
-    <script src="https://preview.babylonjs.com/cannon.js"></script>
+	<script src="https://preview.babylonjs.com/ammo.js"></script>
+	<script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
     <script src="https://preview.babylonjs.com/gltf_validator.js"></script>
     <script src="https://preview.babylonjs.com/babylon.js"></script>

+ 1 - 1
contributing.md

@@ -26,7 +26,7 @@ You can always add to an API, you cannot ever remove anything from one. If the d
 
 ## Forum and Github issues
 
-Since the very beginning, Babylon.js relies on a great forum and a tremendous community: http://www.html5gamedevs.com/forum/16-babylonjs/.
+Since the very beginning, Babylon.js relies on a great forum and a tremendous community: https://forum.babylonjs.com/.
 Please use the forum for **ANY questions you may have**.
 
 Please use the Github issues (after discussing them on the forum) **only** for:

Файловите разлики са ограничени, защото са твърде много
+ 666 - 0
dist/preview release/ammo.js


Файловите разлики са ограничени, защото са твърде много
+ 14263 - 13951
dist/preview release/babylon.d.ts


Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
dist/preview release/babylon.js


Файловите разлики са ограничени, защото са твърде много
+ 934 - 93
dist/preview release/babylon.max.js


Файловите разлики са ограничени, защото са твърде много
+ 934 - 93
dist/preview release/babylon.no-module.max.js


Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
dist/preview release/babylon.worker.js


Файловите разлики са ограничени, защото са твърде много
+ 936 - 95
dist/preview release/es6.js


+ 1 - 1
dist/preview release/glTF2Interface/package.json

@@ -1,7 +1,7 @@
 {
     "name": "babylonjs-gltf2interface",
     "description": "A typescript declaration of babylon's gltf2 inteface.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 239 - 73
dist/preview release/gui/babylon.gui.d.ts

@@ -1,6 +1,7 @@
 /*Babylon.js GUI*/
 // Dependencies for this module:
 //   ../../../../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
                 */
@@ -71,6 +70,22 @@ declare module BABYLON.GUI {
                 */
             onControlPickedObservable: BABYLON.Observable<Control>;
             /**
+                * BABYLON.Observable event triggered before layout is evaluated
+                */
+            onBeginLayoutObservable: BABYLON.Observable<AdvancedDynamicTexture>;
+            /**
+                * BABYLON.Observable event triggered after the layout was evaluated
+                */
+            onEndLayoutObservable: BABYLON.Observable<AdvancedDynamicTexture>;
+            /**
+                * BABYLON.Observable event triggered before the texture is rendered
+                */
+            onBeginRenderObservable: BABYLON.Observable<AdvancedDynamicTexture>;
+            /**
+                * BABYLON.Observable event triggered after the texture was rendered
+                */
+            onEndRenderObservable: BABYLON.Observable<AdvancedDynamicTexture>;
+            /**
                 * Gets or sets a boolean defining if alpha is stored as premultiplied
                 */
             premulAlpha: boolean;
@@ -250,6 +265,47 @@ declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {
     /**
+        * This class can be used to get instrumentation data from a AdvancedDynamicTexture object
+        */
+    export class AdvancedDynamicTextureInstrumentation implements BABYLON.IDisposable {
+            /**
+                * Define the instrumented AdvancedDynamicTexture.
+                */
+            texture: AdvancedDynamicTexture;
+            /**
+                * Gets the perf counter used to capture render time
+                */
+            readonly renderTimeCounter: BABYLON.PerfCounter;
+            /**
+                * Gets the perf counter used to capture layout time
+                */
+            readonly layoutTimeCounter: BABYLON.PerfCounter;
+            /**
+                * Enable or disable the render time capture
+                */
+            captureRenderTime: boolean;
+            /**
+                * Enable or disable the layout time capture
+                */
+            captureLayoutTime: boolean;
+            /**
+                * Instantiates a new advanced dynamic texture instrumentation.
+                * This class can be used to get instrumentation data from an AdvancedDynamicTexture object
+                * @param texture Defines the AdvancedDynamicTexture to instrument
+                */
+            constructor(
+            /**
+                * Define the instrumented AdvancedDynamicTexture.
+                */
+            texture: AdvancedDynamicTexture);
+            /**
+                * Dispose and release associated resources.
+                */
+            dispose(): void;
+    }
+}
+declare module BABYLON.GUI {
+    /**
         * Class used to transport BABYLON.Vector2 information for pointer events
         */
     export class Vector2WithInfo extends BABYLON.Vector2 {
@@ -516,6 +572,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 +787,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;
             /**
@@ -746,7 +809,10 @@ declare module BABYLON.GUI {
             onValueChangedObservable: BABYLON.Observable<BABYLON.Color3>;
             /** Gets or sets the color of the color picker */
             value: BABYLON.Color3;
-            /** Gets or sets control width */
+            /**
+                * Gets or sets control width
+                * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+                */
             width: string | number;
             /** Gets or sets control height */
             height: string | number;
@@ -759,7 +825,9 @@ declare module BABYLON.GUI {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            /** @hidden */
+            _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;
@@ -836,20 +904,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 +943,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 +963,10 @@ declare module BABYLON.GUI {
             /** @hidden */
             protected _verticalAlignment: number;
             /** @hidden */
+            protected _isDirty: boolean;
+            /** @hidden */
+            protected _wasDirty: boolean;
+            /** @hidden */
             _tempParentMeasure: Measure;
             /** @hidden */
             protected _cachedParentMeasure: Measure;
@@ -906,6 +983,10 @@ declare module BABYLON.GUI {
             protected _isEnabled: boolean;
             protected _disabledColor: string;
             /** @hidden */
+            protected _rebuildLayout: boolean;
+            /** @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
@@ -923,6 +1004,10 @@ declare module BABYLON.GUI {
             isFocusInvisible: boolean;
             /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
             clipChildren: boolean;
+            /**
+                * Gets or sets a boolean indicating that the current control should cache its rendering (useful when the control does not change often)
+                */
+            useBitmapCache: boolean;
             /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
             shadowOffsetX: number;
             /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
@@ -980,6 +1065,10 @@ declare module BABYLON.GUI {
                 * An event triggered after the control was drawn
                 */
             onAfterDrawObservable: BABYLON.Observable<Control>;
+            /**
+                * Get the hosting AdvancedDynamicTexture
+                */
+            readonly host: AdvancedDynamicTexture;
             /** Gets or set information about font offsets (used to render and align text) */
             fontOffset: {
                     ascent: number;
@@ -1170,6 +1259,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 +1316,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 +1328,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 +1350,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 +1450,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)
@@ -1384,6 +1499,12 @@ declare module BABYLON.GUI {
                 */
             getChildrenAt(row: number, column: number): BABYLON.Nullable<Array<Control>>;
             /**
+                * Gets a string representing the child cell info (row x column)
+                * @param child defines the control to get info from
+                * @returns a string containing the child cell info (row x column)
+                */
+            getChildCellInfo(child: Control): string;
+            /**
                 * Remove a column definition at specified index
                 * @param index defines the index of the column to remove
                 * @returns the current grid
@@ -1418,7 +1539,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;
     }
@@ -1438,6 +1559,26 @@ declare module BABYLON.GUI {
                 */
             readonly isLoaded: boolean;
             /**
+                * Gets or sets a boolean indicating if nine patch slices (left, top, right, bottom) should be read from image data
+                */
+            populateNinePatchSlicesFromImage: boolean;
+            /**
+                * Gets or sets the left value for slicing (9-patch)
+                */
+            sliceLeft: number;
+            /**
+                * Gets or sets the right value for slicing (9-patch)
+                */
+            sliceRight: number;
+            /**
+                * Gets or sets the top value for slicing (9-patch)
+                */
+            sliceTop: number;
+            /**
+                * Gets or sets the bottom value for slicing (9-patch)
+                */
+            sliceBottom: number;
+            /**
                 * Gets or sets the left coordinate in the source image
                 */
             sourceLeft: number;
@@ -1492,7 +1633,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;
@@ -1502,6 +1644,8 @@ declare module BABYLON.GUI {
             static readonly STRETCH_UNIFORM: number;
             /** STRETCH_EXTEND */
             static readonly STRETCH_EXTEND: number;
+            /** NINE_PATCH */
+            static readonly STRETCH_NINE_PATCH: number;
     }
 }
 declare module BABYLON.GUI {
@@ -1530,6 +1674,8 @@ declare module BABYLON.GUI {
             onTextCutObservable: BABYLON.Observable<InputText>;
             /** BABYLON.Observable raised when paste event is triggered */
             onTextPasteObservable: BABYLON.Observable<InputText>;
+            /** BABYLON.Observable raised when a key event was processed */
+            onKeyboardEventProcessedObservable: BABYLON.Observable<KeyboardEvent>;
             /** Gets or sets the maximum width allowed by the control */
             maxWidth: string | number;
             /** Gets the maximum width allowed by the control in pixels */
@@ -1550,6 +1696,8 @@ declare module BABYLON.GUI {
             thickness: number;
             /** Gets or sets the background color when focused */
             focusedBackground: string;
+            /** Gets or sets the background color when focused */
+            focusedColor: string;
             /** Gets or sets the background color */
             background: string;
             /** Gets or sets the placeholder color */
@@ -1591,8 +1739,9 @@ 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;
+            _onPointerMove(target: Control, coordinates: BABYLON.Vector2): void;
             _onPointerUp(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
             protected _beforeRenderText(text: string): string;
             dispose(): void;
@@ -1634,7 +1783,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 +1858,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 +1889,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 +1926,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,68 +2111,54 @@ 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
+                * Gets the horizontal scrollbar
                 */
-            readonly paddingLeftInPixels: number;
+            readonly horizontalBar: ScrollBar;
             /**
-                * 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
+                * Gets the vertical scrollbar
                 */
-            paddingRight: string | number;
+            readonly verticalBar: ScrollBar;
             /**
-                * 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
+                * */
+            wheelPrecision: number;
             /** Gets or sets the bar color */
             barColor: string;
-            /** Gets or sets the bar color */
-            barBorderColor: string;
+            /** Gets or sets the size of the bar */
+            barSize: number;
             /** 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;
     }
 }
 declare module BABYLON.GUI {
@@ -2104,10 +2242,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;
@@ -2258,7 +2396,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;
     }
 }
@@ -2310,6 +2448,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;
@@ -2321,6 +2461,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 */
@@ -2333,7 +2476,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 {
@@ -2361,7 +2504,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 {
@@ -2895,4 +3038,27 @@ declare module BABYLON.GUI {
             getClassName(): string;
             static Parse(source: any, scene: BABYLON.Scene, rootUrl: string): FluentMaterial;
     }
+}
+declare module BABYLON.GUI {
+    /**
+        * Class used to create slider controls
+        */
+    export class ScrollBar extends BaseSlider {
+            name?: string | undefined;
+            /** Gets or sets border color */
+            borderColor: string;
+            /** Gets or sets background color */
+            background: string;
+            /**
+                * Creates a new Slider
+                * @param name defines the control name
+                */
+            constructor(name?: string | undefined);
+            protected _getTypeName(): string;
+            protected _getThumbThickness(): number;
+            _draw(context: CanvasRenderingContext2D): void;
+            /** @hidden */
+            protected _updateValueFromPointer(x: number, y: number): void;
+            _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
+    }
 }

Файловите разлики са ограничени, защото са твърде много
+ 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


Файловите разлики са ограничени, защото са твърде много
+ 502 - 164
dist/preview release/gui/babylon.gui.module.d.ts


+ 2 - 2
dist/preview release/gui/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-gui",
     "description": "The Babylon.js GUI library is an extension you can use to generate interactive user interface. It is build on top of the DynamicTexture.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.10"
+        "babylonjs": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

Файловите разлики са ограничени, защото са твърде много
+ 8 - 8
dist/preview release/inspector/babylon.inspector.bundle.js


Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.js.map


+ 5 - 5
dist/preview release/inspector/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,10 +28,10 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.10",
-        "babylonjs-gui": "4.0.0-alpha.10",
-        "babylonjs-loaders": "4.0.0-alpha.10",
-        "babylonjs-serializers": "4.0.0-alpha.10"
+        "babylonjs": "4.0.0-alpha.11",
+        "babylonjs-gui": "4.0.0-alpha.11",
+        "babylonjs-loaders": "4.0.0-alpha.11",
+        "babylonjs-serializers": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

Файловите разлики са ограничени, защото са твърде много
+ 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


+ 3 - 3
dist/preview release/loaders/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-loaders",
     "description": "The Babylon.js file loaders library is an extension you can use to load different 3D file types into a Babylon scene.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,8 +27,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "4.0.0-alpha.10",
-        "babylonjs": "4.0.0-alpha.10"
+        "babylonjs-gltf2interface": "4.0.0-alpha.11",
+        "babylonjs": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/materialsLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-materials",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.10"
+        "babylonjs": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

Файловите разлики са ограничени, защото са твърде много
+ 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


+ 2 - 2
dist/preview release/postProcessesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-post-process",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.10"
+        "babylonjs": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

+ 2 - 2
dist/preview release/proceduralTexturesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-procedural-textures",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.10"
+        "babylonjs": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.min.js


Файловите разлики са ограничени, защото са твърде много
+ 1 - 1
dist/preview release/serializers/babylonjs.serializers.min.js


+ 3 - 3
dist/preview release/serializers/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-serializers",
     "description": "The Babylon.js serializers library is an extension you can use to serialize Babylon scenes.",
-    "version": "4.0.0-alpha.10",
+    "version": "4.0.0-alpha.11",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,8 +27,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.10",
-        "babylonjs-gltf2interface": "4.0.0-alpha.10"
+        "babylonjs": "4.0.0-alpha.11",
+        "babylonjs-gltf2interface": "4.0.0-alpha.11"
     },
     "engines": {
         "node": "*"

+ 15 - 1
dist/preview release/viewer/babylon.viewer.d.ts

@@ -924,7 +924,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): (sceneManager: SceneManager) => boolean;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 declare module BabylonViewer {
@@ -1558,6 +1558,20 @@ 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


Файловите разлики са ограничени, защото са твърде много
+ 5 - 5
dist/preview release/viewer/babylon.viewer.max.js


+ 18 - 1
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -985,13 +985,14 @@ 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): (sceneManager: SceneManager) => boolean;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 
@@ -1662,6 +1663,22 @@ 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';

+ 23 - 3
dist/preview release/what's new.md

@@ -4,9 +4,9 @@
 
 - [Inspector v2.0](https://doc.babylonjs.com/features/playground_debuglayer). [Dev log](https://medium.com/@babylonjs/dev-log-creating-the-new-inspector-b15c50900205) ([Deltakosh](https://github.com/deltakosh))
 - Added support for [parallel shader compilation](https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/) ([Deltakosh](https://github.com/deltakosh))
-- Added FlyCamera for free navigation in 3D space, with a limited set of settings ([Phuein](https://github.com/phuein)) [@NEED DEMO]
 - Added [Object Based Motion Blur](http://doc.babylonjs.com/how_to/using_motionblurpostprocess) post-process ([julien-moreau](https://github.com/julien-moreau))
-- WebXR ([TrevorDev](https://github.com/TrevorDev)) [@NEED DEMO]
+- Added [support for AmmoJS](https://doc.babylonjs.com/how_to/using_the_physics_engine) as a physics plugin (Composite objects, joints, motors) ([TrevorDev](https://github.com/TrevorDev))
+- Added support for [WebXR](https://doc.babylonjs.com/how_to/webxr) ([TrevorDev](https://github.com/TrevorDev))
   - Add customAnimationFrameRequester to allow sessions to hook into engine's render loop ([TrevorDev](https://github.com/TrevorDev))
   - camera customDefaultRenderTarget to allow cameras to render to a custom render target (eg. xr framebuffer) instead of the canvas ([TrevorDev](https://github.com/TrevorDev))
   - webXR camera which can be updated by a webXRSession ([TrevorDev](https://github.com/TrevorDev))
@@ -16,14 +16,18 @@
   - WebXRInput manage controllers for the XR experience ([TrevorDev](https://github.com/TrevorDev))
   - WebXR camera rotation using parent container ([TrevorDev](https://github.com/TrevorDev))
 - GUI:
+  - Added `control.useBitmapCache` to optimize re-rendering of complex controls by keeping a cached version ([Deltakosh](https://github.com/deltakosh))
   - 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) for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/))
+  - 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/) / [Deltakosh](https://github.com/deltakosh))
+  - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
+  - Added support for [nine patch stretch](https://www.babylonjs-playground.com/#G5H9IN#2) mode for images. ([Deltakosh](https://github.com/deltakosh))
 
 ## Updates
 
 ### GUI
 
+- Added `inputText.onKeyboardEventProcessedObservable` ([Deltakosh](https://github.com/deltakosh))
 - Added `button.image` and `button.textBlock` to simplify access to button internal parts ([Deltakosh](https://github.com/deltakosh))
 - Added `sldier.displayThumb` to show/hide slider's thumb ([Deltakosh](https://github.com/deltakosh))
 - Added `grid.rowCount`, `grid.columnCount` and `grid.getChildrenAt()` ([Deltakosh](https://github.com/deltakosh))
@@ -32,9 +36,14 @@
 - Added `inputText.onTextCopyObservable`, `inputText.onTextCutObservable` and `inputText.onTextPasteObservable` to inputText ([Saket Saurabh](https://github.com/ssaket))
 - Added `AdvancedDynamicTexture.onClipboardObservable` to observe for clipboard events in AdvancedDynamicTexture([Saket Saurabh](https://github.com/ssaket))
 - Added `inputText.onFocusSelectAll` to allow complete selection of text on focus event.([Saket Saurabh](https://github.com/ssaket))
+- Added mouse drag to highlight text in inputText ([Saket Saurabh](https://github.com/ssaket))
 
 ### Core Engine
 
+- Added `animatable.onAnimationLoopObservable` ([Deltakosh](https://github.com/deltakosh))
+- Added `animationGroup.onAnimationLoopObservable` ([Deltakosh](https://github.com/deltakosh))
+- Added FlyCamera for free navigation in 3D space, with a limited set of settings ([Phuein](https://github.com/phuein))
+- Added support for Scissor testing ([Deltakosh](https://github.com/deltakosh))
 - Added `Engine.onNewSceneAddedObservable` ([Deltakosh](https://github.com/deltakosh))
 - Added new `PassCubePostProcess` to render cube map content ([Deltakosh](https://github.com/deltakosh))
 - Added support for utility layer for SkeletonViewer ([Deltakosh](https://github.com/deltakosh))
@@ -72,6 +81,8 @@
 - Added support for linking a bone to a transform node ([bghgary](https://github.com/bghgary))
 - Factored out `setDirection` function from `lookAt` for transform node ([bghgary](https://github.com/bghgary))
 - Add support for setting renderingGroupId and creating instances to `AxesViewer` ([bghgary](https://github.com/bghgary))
+- Invert vScale of compressed ktx textures as they are inverted in the file and UNPACK_FLIP_Y_WEBGL is not supported by ktx ([TrevorDev](https://github.com/TrevorDev))
+- Enable dragging in boundingBoxGizmo without needing a parent ([TrevorDev](https://github.com/TrevorDev))
 
 ### glTF Loader
 
@@ -90,6 +101,8 @@
 ### Materials Library
 
 ## Bug fixes
+- Fixed TransformNode.setDirection (orientation was wrong) ([Deltakosh](https://github.com/deltakosh))
+- 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))
@@ -105,6 +118,11 @@
 - TransformNode lookAt not working in world space when node's parent has rotation ([TrevorDev](https://github.com/TrevorDev))
 - MakeNotPickableAndWrapInBoundingBox had unexpected behavior when input had scaling of 0 on an axis ([TrevorDev](https://github.com/TrevorDev))
 - Fixed an issue with loading base64 encoded images in the glTF loader ([bghgary](https://github.com/bghgary))
+- In multi-camera scenes the inspector would cause the camera's interaction events to get detached ([TrevorDev](https://github.com/TrevorDev))
+- Fix delete highlighted text after keyboard input, beat delay after double click event in InputText ([Saket Saurabh](https://github.com/ssaket))
+- SixDofDragBehavior will support when the camera is parented ([TrevorDev](https://github.com/TrevorDev))
+- Deactivate webvr lasers when not in vr ([TrevorDev](https://github.com/TrevorDev))
+- Update physics position using absolutePosition instead of pivotPosition ([TrevorDev](https://github.com/TrevorDev))
 
 ### Core Engine
 - Fixed a bug with `mesh.alwaysSelectAsActiveMesh` preventing layerMask to be taken in account ([Deltakosh](https://github.com/deltakosh))
@@ -120,6 +138,8 @@
 - Fixed position and rotation of plane mesh created by MeshBuilder.CreatePlane when specifying a source plane ([sable](https://github.com/thscott), [bghgary](https://github.com/bghgary))
 - Fixed inspector dynamic loading ([Sebavan](https://github.com/Sebavan))
 - Fixed infiniteDistance not working anymore ([Sebavan](https://github.com/Sebavan))
+- Fixed bug in SolidParticle BoundingSphere update within the SolidParticleSystem ([barroij](https://github.com/barroij))
+- Update Picking so that when the picked Mesh is a LinesMesh, the index of the picked line is returned in the `faceId` property of the `PickingInfo`, as we do with face index the picked Mesh is made of triangle faces ([barroij](https://github.com/barroij))
 
 ### Viewer
 

+ 122 - 0
gui/src/2D/adtInstrumentation.ts

@@ -0,0 +1,122 @@
+import { IDisposable, PerfCounter, Nullable, Observer } from "babylonjs";
+import { AdvancedDynamicTexture } from "./advancedDynamicTexture";
+
+/**
+ * This class can be used to get instrumentation data from a AdvancedDynamicTexture object
+ */
+export class AdvancedDynamicTextureInstrumentation implements IDisposable {
+    private _captureRenderTime = false;
+    private _renderTime = new PerfCounter();
+
+    private _captureLayoutTime = false;
+    private _layoutTime = new PerfCounter();
+
+    // Observers
+    private _onBeginRenderObserver: Nullable<Observer<AdvancedDynamicTexture>> = null;
+    private _onEndRenderObserver: Nullable<Observer<AdvancedDynamicTexture>> = null;
+    private _onBeginLayoutObserver: Nullable<Observer<AdvancedDynamicTexture>> = null;
+    private _onEndLayoutObserver: Nullable<Observer<AdvancedDynamicTexture>> = null;
+
+    // Properties
+
+    /**
+     * Gets the perf counter used to capture render time
+     */
+    public get renderTimeCounter(): PerfCounter {
+        return this._renderTime;
+    }
+
+    /**
+     * Gets the perf counter used to capture layout time
+     */
+    public get layoutTimeCounter(): PerfCounter {
+        return this._layoutTime;
+    }
+
+    /**
+     * Enable or disable the render time capture
+     */
+    public get captureRenderTime(): boolean {
+        return this._captureRenderTime;
+    }
+
+    public set captureRenderTime(value: boolean) {
+        if (value === this._captureRenderTime) {
+            return;
+        }
+
+        this._captureRenderTime = value;
+
+        if (value) {
+            this._onBeginRenderObserver = this.texture.onBeginRenderObservable.add(() => {
+                this._renderTime.beginMonitoring();
+            });
+
+            this._onEndRenderObserver = this.texture.onEndRenderObservable.add(() => {
+                this._renderTime.endMonitoring(true);
+            });
+        } else {
+            this.texture.onBeginRenderObservable.remove(this._onBeginRenderObserver);
+            this._onBeginRenderObserver = null;
+            this.texture.onEndRenderObservable.remove(this._onEndRenderObserver);
+            this._onEndRenderObserver = null;
+        }
+    }
+
+    /**
+     * Enable or disable the layout time capture
+     */
+    public get captureLayoutTime(): boolean {
+        return this._captureLayoutTime;
+    }
+
+    public set captureLayoutTime(value: boolean) {
+        if (value === this._captureLayoutTime) {
+            return;
+        }
+
+        this._captureLayoutTime = value;
+
+        if (value) {
+            this._onBeginLayoutObserver = this.texture.onBeginLayoutObservable.add(() => {
+                this._layoutTime.beginMonitoring();
+            });
+
+            this._onEndLayoutObserver = this.texture.onEndLayoutObservable.add(() => {
+                this._layoutTime.endMonitoring(true);
+            });
+        } else {
+            this.texture.onBeginLayoutObservable.remove(this._onBeginLayoutObserver);
+            this._onBeginLayoutObserver = null;
+            this.texture.onEndLayoutObservable.remove(this._onEndLayoutObserver);
+            this._onEndLayoutObserver = null;
+        }
+    }
+    /**
+     * Instantiates a new advanced dynamic texture instrumentation.
+     * This class can be used to get instrumentation data from an AdvancedDynamicTexture object
+     * @param texture Defines the AdvancedDynamicTexture to instrument
+     */
+    public constructor(
+        /**
+         * Define the instrumented AdvancedDynamicTexture.
+         */
+        public texture: AdvancedDynamicTexture) {
+    }
+
+    /**
+     * Dispose and release associated resources.
+     */
+    public dispose() {
+        this.texture.onBeginRenderObservable.remove(this._onBeginRenderObserver);
+        this._onBeginRenderObserver = null;
+        this.texture.onEndRenderObservable.remove(this._onEndRenderObserver);
+        this._onEndRenderObserver = null;
+        this.texture.onBeginLayoutObservable.remove(this._onBeginLayoutObserver);
+        this._onBeginLayoutObserver = null;
+        this.texture.onEndLayoutObservable.remove(this._onEndLayoutObserver);
+        this._onEndLayoutObserver = null;
+
+        (<any>this.texture) = null;
+    }
+}

+ 37 - 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
@@ -89,6 +86,26 @@ export class AdvancedDynamicTexture extends DynamicTexture {
     public onControlPickedObservable = new Observable<Control>();
 
     /**
+     * Observable event triggered before layout is evaluated
+     */
+    public onBeginLayoutObservable = new Observable<AdvancedDynamicTexture>();
+
+    /**
+     * Observable event triggered after the layout was evaluated
+     */
+    public onEndLayoutObservable = new Observable<AdvancedDynamicTexture>();
+
+    /**
+     * Observable event triggered before the texture is rendered
+     */
+    public onBeginRenderObservable = new Observable<AdvancedDynamicTexture>();
+
+    /**
+     * Observable event triggered after the texture was rendered
+     */
+    public onEndRenderObservable = new Observable<AdvancedDynamicTexture>();
+
+    /**
      * Gets or sets a boolean defining if alpha is stored as premultiplied
      */
     public premulAlpha = false;
@@ -317,7 +334,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
             info.skipOnPointerObservable = true;
         });
 
-        this._rootContainer._link(null, this);
+        this._rootContainer._link(this);
 
         this.hasAlpha = true;
 
@@ -438,6 +455,10 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         this._rootContainer.dispose();
         this.onClipboardObservable.clear();
         this.onControlPickedObservable.clear();
+        this.onBeginRenderObservable.clear();
+        this.onEndRenderObservable.clear();
+        this.onBeginLayoutObservable.clear();
+        this.onEndLayoutObservable.clear();
 
         super.dispose();
     }
@@ -575,13 +596,17 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         // Render
         context.font = "18px Arial";
         context.strokeStyle = "white";
+
+        this.onBeginLayoutObservable.notifyObservers(this);
         var measure = new Measure(0, 0, renderWidth, renderHeight);
-        this._rootContainer._draw(measure, context);
+        this._rootContainer._layout(measure, context);
+        this.onEndLayoutObservable.notifyObservers(this);
 
-        if (this._needRedraw) { // We need to redraw as some elements dynamically adapt to their content
-            this._needRedraw = false;
-            this._render();
-        }
+        this._isDirty = false; // Restoring the dirty state that could have been set by controls during layout processing
+
+        this.onBeginRenderObservable.notifyObservers(this);
+        this._rootContainer._render(context);
+        this.onEndRenderObservable.notifyObservers(this);
     }
 
     /** @hidden */
@@ -833,16 +858,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();
     }
 

+ 487 - 439
gui/src/2D/controls/colorpicker.ts

@@ -1,440 +1,488 @@
-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 {
-    private _colorWheelCanvas: HTMLCanvasElement;
-
-    private _value: Color3 = Color3.Red();
-    private _tmpColor = new Color3();
-
-    private _pointerStartedOnSquare = false;
-    private _pointerStartedOnWheel = false;
-
-    private _squareLeft = 0;
-    private _squareTop = 0;
-    private _squareSize = 0;
-
-    private _h = 360;
-    private _s = 1;
-    private _v = 1;
-
-    /**
-     * Observable raised when the value changes
-     */
-    public onValueChangedObservable = new Observable<Color3>();
-
-    /** Gets or sets the color of the color picker */
-    public get value(): Color3 {
-        return this._value;
-    }
-
-    public set value(value: Color3) {
-        if (this._value.equals(value)) {
-            return;
-        }
-
-        this._value.copyFrom(value);
-
-        this._RGBtoHSV(this._value, this._tmpColor);
-
-        this._h = this._tmpColor.r;
-        this._s = Math.max(this._tmpColor.g, 0.00001);
-        this._v = Math.max(this._tmpColor.b, 0.00001);
-
-        this._markAsDirty();
-
-        this.onValueChangedObservable.notifyObservers(this._value);
-    }
-
-    /** Gets or sets control width */
-    public set width(value: string | number) {
-        if (this._width.toString(this._host) === value) {
-            return;
-        }
-
-        if (this._width.fromString(value)) {
-            this._height.fromString(value);
-            this._markAsDirty();
-        }
-    }
-
-    /** Gets or sets control height */
-    public set height(value: string | number) {
-        if (this._height.toString(this._host) === value) {
-            return;
-        }
-
-        if (this._height.fromString(value)) {
-            this._width.fromString(value);
-            this._markAsDirty();
-        }
-    }
-
-    /** Gets or sets control size */
-    public get size(): string | number {
-        return this.width;
-    }
-
-    public set size(value: string | number) {
-        this.width = value;
-    }
-
-    /**
-     * Creates a new ColorPicker
-     * @param name defines the control name
-     */
-    constructor(public name?: string) {
-        super(name);
-        this.value = new Color3(.88, .1, .1);
-        this.size = "200px";
-        this.isPointerBlocker = true;
-    }
-
-    protected _getTypeName(): string {
-        return "ColorPicker";
-    }
-
-    private _updateSquareProps(): void {
-        var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
-        var wheelThickness = radius * .2;
-        var innerDiameter = (radius - wheelThickness) * 2;
-        var squareSize = innerDiameter / (Math.sqrt(2));
-        var offset = radius - squareSize * .5;
-
-        this._squareLeft = this._currentMeasure.left + offset;
-        this._squareTop = this._currentMeasure.top + offset;
-        this._squareSize = squareSize;
-    }
-
-    private _drawGradientSquare(hueValue: number, left: number, top: number, width: number, height: number, context: CanvasRenderingContext2D) {
-        var lgh = context.createLinearGradient(left, top, width + left, top);
-        lgh.addColorStop(0, '#fff');
-        lgh.addColorStop(1, 'hsl(' + hueValue + ', 100%, 50%)');
-
-        context.fillStyle = lgh;
-        context.fillRect(left, top, width, height);
-
-        var lgv = context.createLinearGradient(left, top, left, height + top);
-        lgv.addColorStop(0, 'rgba(0,0,0,0)');
-        lgv.addColorStop(1, '#000');
-
-        context.fillStyle = lgv;
-        context.fillRect(left, top, width, height);
-    }
-
-    private _drawCircle(centerX: number, centerY: number, radius: number, context: CanvasRenderingContext2D) {
-        context.beginPath();
-        context.arc(centerX, centerY, radius + 1, 0, 2 * Math.PI, false);
-        context.lineWidth = 3;
-        context.strokeStyle = '#333333';
-        context.stroke();
-        context.beginPath();
-        context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
-        context.lineWidth = 3;
-        context.strokeStyle = '#ffffff';
-        context.stroke();
-    }
-
-    private _createColorWheelCanvas(radius: number, thickness: number): HTMLCanvasElement {
-        var canvas = document.createElement("canvas");
-        canvas.width = radius * 2;
-        canvas.height = radius * 2;
-        var context = <CanvasRenderingContext2D>canvas.getContext("2d");
-        var image = context.getImageData(0, 0, radius * 2, radius * 2);
-        var data = image.data;
-
-        var color = this._tmpColor;
-        var maxDistSq = radius * radius;
-        var innerRadius = radius - thickness;
-        var minDistSq = innerRadius * innerRadius;
-
-        for (var x = -radius; x < radius; x++) {
-            for (var y = -radius; y < radius; y++) {
-
-                var distSq = x * x + y * y;
-
-                if (distSq > maxDistSq || distSq < minDistSq) {
-                    continue;
-                }
-
-                var dist = Math.sqrt(distSq);
-                var ang = Math.atan2(y, x);
-
-                this._HSVtoRGB(ang * 180 / Math.PI + 180, dist / radius, 1, color);
-
-                var index = ((x + radius) + ((y + radius) * 2 * radius)) * 4;
-
-                data[index] = color.r * 255;
-                data[index + 1] = color.g * 255;
-                data[index + 2] = color.b * 255;
-                var alphaRatio = (dist - innerRadius) / (radius - innerRadius);
-
-                //apply less alpha to bigger color pickers
-                var alphaAmount = .2;
-                var maxAlpha = .2;
-                var minAlpha = .04;
-                var lowerRadius = 50;
-                var upperRadius = 150;
-
-                if (radius < lowerRadius) {
-                    alphaAmount = maxAlpha;
-                } else if (radius > upperRadius) {
-                    alphaAmount = minAlpha;
-                } else {
-                    alphaAmount = (minAlpha - maxAlpha) * (radius - lowerRadius) / (upperRadius - lowerRadius) + maxAlpha;
-                }
-
-                var alphaRatio = (dist - innerRadius) / (radius - innerRadius);
-
-                if (alphaRatio < alphaAmount) {
-                    data[index + 3] = 255 * (alphaRatio / alphaAmount);
-                } else if (alphaRatio > 1 - alphaAmount) {
-                    data[index + 3] = 255 * (1.0 - ((alphaRatio - (1 - alphaAmount)) / alphaAmount));
-                } else {
-                    data[index + 3] = 255;
-                }
-
-            }
-        }
-
-        context.putImageData(image, 0, 0);
-
-        return canvas;
-    }
-
-    private _RGBtoHSV(color: Color3, result: Color3) {
-        var r = color.r;
-        var g = color.g;
-        var b = color.b;
-
-        var max = Math.max(r, g, b);
-        var min = Math.min(r, g, b);
-        var h = 0;
-        var s = 0;
-        var v = max;
-
-        var dm = max - min;
-
-        if (max !== 0) {
-            s = dm / max;
-        }
-
-        if (max != min) {
-            if (max == r) {
-                h = (g - b) / dm;
-                if (g < b) {
-                    h += 6;
-                }
-            } else if (max == g) {
-                h = (b - r) / dm + 2;
-            } else if (max == b) {
-                h = (r - g) / dm + 4;
-            }
-            h *= 60;
-        }
-
-        result.r = h;
-        result.g = s;
-        result.b = v;
-    }
-
-    private _HSVtoRGB(hue: number, saturation: number, value: number, result: Color3) {
-        var chroma = value * saturation;
-        var h = hue / 60;
-        var x = chroma * (1 - Math.abs((h % 2) - 1));
-        var r = 0;
-        var g = 0;
-        var b = 0;
-
-        if (h >= 0 && h <= 1) {
-            r = chroma;
-            g = x;
-        } else if (h >= 1 && h <= 2) {
-            r = x;
-            g = chroma;
-        } else if (h >= 2 && h <= 3) {
-            g = chroma;
-            b = x;
-        } else if (h >= 3 && h <= 4) {
-            g = x;
-            b = chroma;
-        } else if (h >= 4 && h <= 5) {
-            r = x;
-            b = chroma;
-        } else if (h >= 5 && h <= 6) {
-            r = chroma;
-            b = x;
-        }
-
-        var m = value - chroma;
-        result.set((r + m), (g + m), (b + m));
-    }
-
-    /** @hidden */
-    public _draw(parentMeasure: Measure, 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;
-
-            if (!this._colorWheelCanvas || this._colorWheelCanvas.width != radius * 2) {
-                this._colorWheelCanvas = this._createColorWheelCanvas(radius, wheelThickness);
-            }
-
-            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;
-
-                context.fillRect(this._squareLeft, this._squareTop, this._squareSize, this._squareSize);
-            }
-
-            context.drawImage(this._colorWheelCanvas, left, top);
-
-            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);
-
-            var cx = this._squareLeft + this._squareSize * this._s;
-            var cy = this._squareTop + this._squareSize * (1 - this._v);
-
-            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);
-
-        }
-        context.restore();
-    }
-
-    // Events
-    private _pointerIsDown = false;
-
-    private _updateValueFromPointer(x: number, y: number): void {
-        if (this._pointerStartedOnWheel) {
-            var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
-            var centerX = radius + this._currentMeasure.left;
-            var centerY = radius + this._currentMeasure.top;
-            this._h = Math.atan2(y - centerY, x - centerX) * 180 / Math.PI + 180;
-        }
-        else if (this._pointerStartedOnSquare) {
-            this._updateSquareProps();
-            this._s = (x - this._squareLeft) / this._squareSize;
-            this._v = 1 - (y - this._squareTop) / this._squareSize;
-            this._s = Math.min(this._s, 1);
-            this._s = Math.max(this._s, 0.00001);
-            this._v = Math.min(this._v, 1);
-            this._v = Math.max(this._v, 0.00001);
-        }
-
-        this._HSVtoRGB(this._h, this._s, this._v, this._tmpColor);
-
-        this.value = this._tmpColor;
-    }
-
-    private _isPointOnSquare(x: number, y: number): boolean {
-        this._updateSquareProps();
-
-        var left = this._squareLeft;
-        var top = this._squareTop;
-        var size = this._squareSize;
-
-        if (x >= left && x <= left + size &&
-            y >= top && y <= top + size) {
-            return true;
-        }
-
-        return false;
-    }
-
-    private _isPointOnWheel(x: number, y: number): boolean {
-        var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
-        var centerX = radius + this._currentMeasure.left;
-        var centerY = radius + this._currentMeasure.top;
-        var wheelThickness = radius * .2;
-        var innerRadius = radius - wheelThickness;
-        var radiusSq = radius * radius;
-        var innerRadiusSq = innerRadius * innerRadius;
-
-        var dx = x - centerX;
-        var dy = y - centerY;
-
-        var distSq = dx * dx + dy * dy;
-
-        if (distSq <= radiusSq && distSq >= innerRadiusSq) {
-            return true;
-        }
-
-        return false;
-    }
-
-    public _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean {
-        if (!super._onPointerDown(target, coordinates, pointerId, buttonIndex)) {
-            return false;
-        }
-
-        this._pointerIsDown = true;
-
-        this._pointerStartedOnSquare = false;
-        this._pointerStartedOnWheel = false;
-
-        // Invert transform
-        this._invertTransformMatrix.transformCoordinates(coordinates.x, coordinates.y, this._transformedPosition);
-
-        let x = this._transformedPosition.x;
-        let y = this._transformedPosition.y;
-
-        if (this._isPointOnSquare(x, y)) {
-            this._pointerStartedOnSquare = true;
-        } else if (this._isPointOnWheel(x, y)) {
-            this._pointerStartedOnWheel = true;
-        }
-
-        this._updateValueFromPointer(x, y);
-        this._host._capturingControl[pointerId] = this;
-
-        return true;
-    }
-
-    public _onPointerMove(target: Control, coordinates: Vector2): void {
-        // Invert transform
-        this._invertTransformMatrix.transformCoordinates(coordinates.x, coordinates.y, this._transformedPosition);
-
-        let x = this._transformedPosition.x;
-        let y = this._transformedPosition.y;
-
-        if (this._pointerIsDown) {
-            this._updateValueFromPointer(x, y);
-        }
-
-        super._onPointerMove(target, coordinates);
-    }
-
-    public _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void {
-        this._pointerIsDown = false;
-
-        delete this._host._capturingControl[pointerId];
-        super._onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick);
-    }
+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 {
+    private static _Epsilon = 0.000001;
+    private _colorWheelCanvas: HTMLCanvasElement;
+
+    private _value: Color3 = Color3.Red();
+    private _tmpColor = new Color3();
+
+    private _pointerStartedOnSquare = false;
+    private _pointerStartedOnWheel = false;
+
+    private _squareLeft = 0;
+    private _squareTop = 0;
+    private _squareSize = 0;
+
+    private _h = 360;
+    private _s = 1;
+    private _v = 1;
+
+    /**
+     * Observable raised when the value changes
+     */
+    public onValueChangedObservable = new Observable<Color3>();
+
+    /** Gets or sets the color of the color picker */
+    public get value(): Color3 {
+        return this._value;
+    }
+
+    public set value(value: Color3) {
+        if (this._value.equals(value)) {
+            return;
+        }
+
+        this._value.copyFrom(value);
+
+        this._RGBtoHSV(this._value, this._tmpColor);
+
+        this._h = this._tmpColor.r;
+        this._s = Math.max(this._tmpColor.g, 0.00001);
+        this._v = Math.max(this._tmpColor.b, 0.00001);
+
+        this._markAsDirty();
+
+        if (this._value.r <= ColorPicker._Epsilon) {
+            this._value.r = 0;
+        }
+
+        if (this._value.g <= ColorPicker._Epsilon) {
+            this._value.g = 0;
+        }
+
+        if (this._value.b <= ColorPicker._Epsilon) {
+            this._value.b = 0;
+        }
+
+        if (this._value.r >= 1.0 - ColorPicker._Epsilon) {
+            this._value.r = 1.0;
+        }
+
+        if (this._value.g >= 1.0 - ColorPicker._Epsilon) {
+            this._value.g = 1.0;
+        }
+
+        if (this._value.b >= 1.0 - ColorPicker._Epsilon) {
+            this._value.b = 1.0;
+        }
+
+        this.onValueChangedObservable.notifyObservers(this._value);
+    }
+
+    /**
+     * Gets or sets control width
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get width(): string | number {
+        return this._width.toString(this._host);
+    }
+
+    public set width(value: string | number) {
+        if (this._width.toString(this._host) === value) {
+            return;
+        }
+
+        if (this._width.fromString(value)) {
+            this._height.fromString(value);
+            this._markAsDirty();
+        }
+    }
+
+    /**
+     * Gets or sets control height
+     * @see http://doc.babylonjs.com/how_to/gui#position-and-size
+     */
+    public get height(): string | number {
+        return this._height.toString(this._host);
+    }
+
+    /** Gets or sets control height */
+    public set height(value: string | number) {
+        if (this._height.toString(this._host) === value) {
+            return;
+        }
+
+        if (this._height.fromString(value)) {
+            this._width.fromString(value);
+            this._markAsDirty();
+        }
+    }
+
+    /** Gets or sets control size */
+    public get size(): string | number {
+        return this.width;
+    }
+
+    public set size(value: string | number) {
+        this.width = value;
+    }
+
+    /**
+     * Creates a new ColorPicker
+     * @param name defines the control name
+     */
+    constructor(public name?: string) {
+        super(name);
+        this.value = new Color3(.88, .1, .1);
+        this.size = "200px";
+        this.isPointerBlocker = true;
+    }
+
+    protected _getTypeName(): string {
+        return "ColorPicker";
+    }
+
+    /** @hidden */
+    protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+
+        if (parentMeasure.width < parentMeasure.height) {
+            this._currentMeasure.height = parentMeasure.width;
+        } else {
+            this._currentMeasure.width = parentMeasure.height;
+        }
+    }
+
+    private _updateSquareProps(): void {
+        var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
+        var wheelThickness = radius * .2;
+        var innerDiameter = (radius - wheelThickness) * 2;
+        var squareSize = innerDiameter / (Math.sqrt(2));
+        var offset = radius - squareSize * .5;
+
+        this._squareLeft = this._currentMeasure.left + offset;
+        this._squareTop = this._currentMeasure.top + offset;
+        this._squareSize = squareSize;
+    }
+
+    private _drawGradientSquare(hueValue: number, left: number, top: number, width: number, height: number, context: CanvasRenderingContext2D) {
+        var lgh = context.createLinearGradient(left, top, width + left, top);
+        lgh.addColorStop(0, '#fff');
+        lgh.addColorStop(1, 'hsl(' + hueValue + ', 100%, 50%)');
+
+        context.fillStyle = lgh;
+        context.fillRect(left, top, width, height);
+
+        var lgv = context.createLinearGradient(left, top, left, height + top);
+        lgv.addColorStop(0, 'rgba(0,0,0,0)');
+        lgv.addColorStop(1, '#000');
+
+        context.fillStyle = lgv;
+        context.fillRect(left, top, width, height);
+    }
+
+    private _drawCircle(centerX: number, centerY: number, radius: number, context: CanvasRenderingContext2D) {
+        context.beginPath();
+        context.arc(centerX, centerY, radius + 1, 0, 2 * Math.PI, false);
+        context.lineWidth = 3;
+        context.strokeStyle = '#333333';
+        context.stroke();
+        context.beginPath();
+        context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
+        context.lineWidth = 3;
+        context.strokeStyle = '#ffffff';
+        context.stroke();
+    }
+
+    private _createColorWheelCanvas(radius: number, thickness: number): HTMLCanvasElement {
+        var canvas = document.createElement("canvas");
+        canvas.width = radius * 2;
+        canvas.height = radius * 2;
+        var context = <CanvasRenderingContext2D>canvas.getContext("2d");
+        var image = context.getImageData(0, 0, radius * 2, radius * 2);
+        var data = image.data;
+
+        var color = this._tmpColor;
+        var maxDistSq = radius * radius;
+        var innerRadius = radius - thickness;
+        var minDistSq = innerRadius * innerRadius;
+
+        for (var x = -radius; x < radius; x++) {
+            for (var y = -radius; y < radius; y++) {
+
+                var distSq = x * x + y * y;
+
+                if (distSq > maxDistSq || distSq < minDistSq) {
+                    continue;
+                }
+
+                var dist = Math.sqrt(distSq);
+                var ang = Math.atan2(y, x);
+
+                this._HSVtoRGB(ang * 180 / Math.PI + 180, dist / radius, 1, color);
+
+                var index = ((x + radius) + ((y + radius) * 2 * radius)) * 4;
+
+                data[index] = color.r * 255;
+                data[index + 1] = color.g * 255;
+                data[index + 2] = color.b * 255;
+                var alphaRatio = (dist - innerRadius) / (radius - innerRadius);
+
+                //apply less alpha to bigger color pickers
+                var alphaAmount = .2;
+                var maxAlpha = .2;
+                var minAlpha = .04;
+                var lowerRadius = 50;
+                var upperRadius = 150;
+
+                if (radius < lowerRadius) {
+                    alphaAmount = maxAlpha;
+                } else if (radius > upperRadius) {
+                    alphaAmount = minAlpha;
+                } else {
+                    alphaAmount = (minAlpha - maxAlpha) * (radius - lowerRadius) / (upperRadius - lowerRadius) + maxAlpha;
+                }
+
+                var alphaRatio = (dist - innerRadius) / (radius - innerRadius);
+
+                if (alphaRatio < alphaAmount) {
+                    data[index + 3] = 255 * (alphaRatio / alphaAmount);
+                } else if (alphaRatio > 1 - alphaAmount) {
+                    data[index + 3] = 255 * (1.0 - ((alphaRatio - (1 - alphaAmount)) / alphaAmount));
+                } else {
+                    data[index + 3] = 255;
+                }
+
+            }
+        }
+
+        context.putImageData(image, 0, 0);
+
+        return canvas;
+    }
+
+    private _RGBtoHSV(color: Color3, result: Color3) {
+        var r = color.r;
+        var g = color.g;
+        var b = color.b;
+
+        var max = Math.max(r, g, b);
+        var min = Math.min(r, g, b);
+        var h = 0;
+        var s = 0;
+        var v = max;
+
+        var dm = max - min;
+
+        if (max !== 0) {
+            s = dm / max;
+        }
+
+        if (max != min) {
+            if (max == r) {
+                h = (g - b) / dm;
+                if (g < b) {
+                    h += 6;
+                }
+            } else if (max == g) {
+                h = (b - r) / dm + 2;
+            } else if (max == b) {
+                h = (r - g) / dm + 4;
+            }
+            h *= 60;
+        }
+
+        result.r = h;
+        result.g = s;
+        result.b = v;
+    }
+
+    private _HSVtoRGB(hue: number, saturation: number, value: number, result: Color3) {
+        var chroma = value * saturation;
+        var h = hue / 60;
+        var x = chroma * (1 - Math.abs((h % 2) - 1));
+        var r = 0;
+        var g = 0;
+        var b = 0;
+
+        if (h >= 0 && h <= 1) {
+            r = chroma;
+            g = x;
+        } else if (h >= 1 && h <= 2) {
+            r = x;
+            g = chroma;
+        } else if (h >= 2 && h <= 3) {
+            g = chroma;
+            b = x;
+        } else if (h >= 3 && h <= 4) {
+            g = x;
+            b = chroma;
+        } else if (h >= 4 && h <= 5) {
+            r = x;
+            b = chroma;
+        } else if (h >= 5 && h <= 6) {
+            r = chroma;
+            b = x;
+        }
+
+        var m = value - chroma;
+        result.set((r + m), (g + m), (b + m));
+    }
+
+    /** @hidden */
+    public _draw(context: CanvasRenderingContext2D): void {
+        context.save();
+
+        this._applyStates(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;
+
+        if (!this._colorWheelCanvas || this._colorWheelCanvas.width != radius * 2) {
+            this._colorWheelCanvas = this._createColorWheelCanvas(radius, wheelThickness);
+        }
+
+        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;
+
+            context.fillRect(this._squareLeft, this._squareTop, this._squareSize, this._squareSize);
+        }
+
+        context.drawImage(this._colorWheelCanvas, left, top);
+
+        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);
+
+        var cx = this._squareLeft + this._squareSize * this._s;
+        var cy = this._squareTop + this._squareSize * (1 - this._v);
+
+        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);
+
+        context.restore();
+    }
+
+    // Events
+    private _pointerIsDown = false;
+
+    private _updateValueFromPointer(x: number, y: number): void {
+        if (this._pointerStartedOnWheel) {
+            var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
+            var centerX = radius + this._currentMeasure.left;
+            var centerY = radius + this._currentMeasure.top;
+            this._h = Math.atan2(y - centerY, x - centerX) * 180 / Math.PI + 180;
+        }
+        else if (this._pointerStartedOnSquare) {
+            this._updateSquareProps();
+            this._s = (x - this._squareLeft) / this._squareSize;
+            this._v = 1 - (y - this._squareTop) / this._squareSize;
+            this._s = Math.min(this._s, 1);
+            this._s = Math.max(this._s, ColorPicker._Epsilon);
+            this._v = Math.min(this._v, 1);
+            this._v = Math.max(this._v, ColorPicker._Epsilon);
+        }
+
+        this._HSVtoRGB(this._h, this._s, this._v, this._tmpColor);
+
+        this.value = this._tmpColor;
+    }
+
+    private _isPointOnSquare(x: number, y: number): boolean {
+        this._updateSquareProps();
+
+        var left = this._squareLeft;
+        var top = this._squareTop;
+        var size = this._squareSize;
+
+        if (x >= left && x <= left + size &&
+            y >= top && y <= top + size) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private _isPointOnWheel(x: number, y: number): boolean {
+        var radius = Math.min(this._currentMeasure.width, this._currentMeasure.height) * .5;
+        var centerX = radius + this._currentMeasure.left;
+        var centerY = radius + this._currentMeasure.top;
+        var wheelThickness = radius * .2;
+        var innerRadius = radius - wheelThickness;
+        var radiusSq = radius * radius;
+        var innerRadiusSq = innerRadius * innerRadius;
+
+        var dx = x - centerX;
+        var dy = y - centerY;
+
+        var distSq = dx * dx + dy * dy;
+
+        if (distSq <= radiusSq && distSq >= innerRadiusSq) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public _onPointerDown(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number): boolean {
+        if (!super._onPointerDown(target, coordinates, pointerId, buttonIndex)) {
+            return false;
+        }
+
+        this._pointerIsDown = true;
+
+        this._pointerStartedOnSquare = false;
+        this._pointerStartedOnWheel = false;
+
+        // Invert transform
+        this._invertTransformMatrix.transformCoordinates(coordinates.x, coordinates.y, this._transformedPosition);
+
+        let x = this._transformedPosition.x;
+        let y = this._transformedPosition.y;
+
+        if (this._isPointOnSquare(x, y)) {
+            this._pointerStartedOnSquare = true;
+        } else if (this._isPointOnWheel(x, y)) {
+            this._pointerStartedOnWheel = true;
+        }
+
+        this._updateValueFromPointer(x, y);
+        this._host._capturingControl[pointerId] = this;
+
+        return true;
+    }
+
+    public _onPointerMove(target: Control, coordinates: Vector2): void {
+        // Invert transform
+        this._invertTransformMatrix.transformCoordinates(coordinates.x, coordinates.y, this._transformedPosition);
+
+        let x = this._transformedPosition.x;
+        let y = this._transformedPosition.y;
+
+        if (this._pointerIsDown) {
+            this._updateValueFromPointer(x, y);
+        }
+
+        super._onPointerMove(target, coordinates);
+    }
+
+    public _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void {
+        this._pointerIsDown = false;
+
+        delete this._host._capturingControl[pointerId];
+        super._onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick);
+    }
 }

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

@@ -149,7 +149,7 @@ export class Container extends Control {
         if (index !== -1) {
             return this;
         }
-        control._link(this, this._host);
+        control._link(this._host);
 
         control._markAllAsDirty();
 
@@ -216,6 +216,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();
 
@@ -227,6 +245,7 @@ export class Container extends Control {
     /** @hidden */
     protected _localDraw(context: CanvasRenderingContext2D): void {
         if (this._background) {
+            context.save();
             if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
                 context.shadowColor = this.shadowColor;
                 context.shadowBlur = this.shadowBlur;
@@ -236,86 +255,104 @@ export class Container extends Control {
 
             context.fillStyle = this._background;
             context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
-
-            if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
-                context.shadowBlur = 0;
-                context.shadowOffsetX = 0;
-                context.shadowOffsetY = 0;
-            }
+            context.restore();
         }
     }
 
     /** @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 +404,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);
 

+ 185 - 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,10 @@ export class Control {
     protected _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
     /** @hidden */
     protected _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
-    private _isDirty = true;
+    /** @hidden */
+    protected _isDirty = true;
+    /** @hidden */
+    protected _wasDirty = false;
     /** @hidden */
     public _tempParentMeasure = Measure.Empty();
     /** @hidden */
@@ -68,7 +69,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;
@@ -85,6 +85,12 @@ export class Control {
     protected _isEnabled = true;
     protected _disabledColor = "#9a9a9a";
     /** @hidden */
+    protected _rebuildLayout = false;
+
+    /** @hidden */
+    public _isClipped = false;
+
+    /** @hidden */
     public _tag: any;
 
     /**
@@ -107,6 +113,13 @@ export class Control {
     /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
     public clipChildren = true;
 
+    /**
+     * Gets or sets a boolean indicating that the current control should cache its rendering (useful when the control does not change often)
+     */
+    public useBitmapCache = false;
+
+    private _cacheData: Nullable<ImageData>;
+
     private _shadowOffsetX = 0;
     /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
     public get shadowOffsetX() {
@@ -235,6 +248,13 @@ export class Control {
      */
     public onAfterDrawObservable = new Observable<Control>();
 
+    /**
+     * Get the hosting AdvancedDynamicTexture
+     */
+    public get host(): AdvancedDynamicTexture {
+        return this._host;
+    }
+
     /** Gets or set information about font offsets (used to render and align text) */
     public get fontOffset(): { ascent: number, height: number, descent: number } {
         return this._fontOffset;
@@ -575,8 +595,8 @@ export class Control {
 
         this._zIndex = value;
 
-        if (this._root) {
-            this._root._reOrderControl(this);
+        if (this.parent) {
+            this.parent._reOrderControl(this);
         }
     }
 
@@ -848,6 +868,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 +953,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 +998,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 +1019,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 +1048,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 +1094,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 +1128,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 +1149,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 +1180,37 @@ 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);
+
+        let rebuildCount = 0;
+        do {
+            this._rebuildLayout = false;
+            this._processMeasures(parentMeasure, context);
+            rebuildCount++;
+        }
+        while (this._rebuildLayout && rebuildCount < 3);
+
+        if (rebuildCount >= 3) {
+            BABYLON.Tools.Error(`Layout cycle detected in GUI (Control uniqueId=${this.uniqueId})`);
+        }
+
+        context.restore();
+
+        this._wasDirty = this._isDirty;
+        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 +1235,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 +1368,79 @@ 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);
+        }
+
+        if (this.useBitmapCache && !this._wasDirty && this._cacheData) {
+            context.putImageData(this._cacheData, this._currentMeasure.left, this._currentMeasure.top);
+        } else {
+            this._draw(context);
+        }
+
+        if (this.useBitmapCache && this._wasDirty) {
+            this._cacheData = context.getImageData(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+        }
+
+        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 +1663,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;

+ 46 - 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();
@@ -114,6 +150,15 @@ export class Grid extends Container {
         return cell.children;
     }
 
+    /**
+     * Gets a string representing the child cell info (row x column)
+     * @param child defines the control to get info from
+     * @returns a string containing the child cell info (row x column)
+     */
+    public getChildCellInfo(child: Control): string {
+        return child._tag;
+    }
+
     private _removeCell(cell: Container, key: string) {
         if (!cell) {
             return;
@@ -388,11 +433,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[]) => {

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

@@ -1,11 +1,13 @@
 import { Control } from "./control";
 import { Nullable, Tools, Observable } from "babylonjs";
-import { Measure } from "../measure";
+import { Measure } from "2D";
 
 /**
  * Class used to create 2D images
  */
 export class Image extends Control {
+    private static _WorkingCanvas: Nullable<HTMLCanvasElement> = null;
+
     private _domImage: HTMLImageElement;
     private _imageWidth: number;
     private _imageHeight: number;
@@ -23,6 +25,12 @@ export class Image extends Control {
     private _cellHeight: number = 0;
     private _cellId: number = -1;
 
+    private _populateNinePatchSlicesFromImage = false;
+    private _sliceLeft: number;
+    private _sliceRight: number;
+    private _sliceTop: number;
+    private _sliceBottom: number;
+
     /**
      * Observable notified when the content is loaded
      */
@@ -36,6 +44,93 @@ export class Image extends Control {
     }
 
     /**
+     * Gets or sets a boolean indicating if nine patch slices (left, top, right, bottom) should be read from image data
+     */
+    public get populateNinePatchSlicesFromImage(): boolean {
+        return this._populateNinePatchSlicesFromImage;
+    }
+
+    public set populateNinePatchSlicesFromImage(value: boolean) {
+        if (this._populateNinePatchSlicesFromImage === value) {
+            return;
+        }
+
+        this._populateNinePatchSlicesFromImage = value;
+
+        if (this._populateNinePatchSlicesFromImage && this._loaded) {
+            this._extractNinePatchSliceDataFromImage();
+        }
+    }
+
+    /**
+     * Gets or sets the left value for slicing (9-patch)
+     */
+    public get sliceLeft(): number {
+        return this._sliceLeft;
+    }
+
+    public set sliceLeft(value: number) {
+        if (this._sliceLeft === value) {
+            return;
+        }
+
+        this._sliceLeft = value;
+
+        this._markAsDirty();
+    }
+
+    /**
+     * Gets or sets the right value for slicing (9-patch)
+     */
+    public get sliceRight(): number {
+        return this._sliceRight;
+    }
+
+    public set sliceRight(value: number) {
+        if (this._sliceRight === value) {
+            return;
+        }
+
+        this._sliceRight = value;
+
+        this._markAsDirty();
+    }
+
+    /**
+     * Gets or sets the top value for slicing (9-patch)
+     */
+    public get sliceTop(): number {
+        return this._sliceTop;
+    }
+
+    public set sliceTop(value: number) {
+        if (this._sliceTop === value) {
+            return;
+        }
+
+        this._sliceTop = value;
+
+        this._markAsDirty();
+    }
+
+    /**
+     * Gets or sets the bottom value for slicing (9-patch)
+     */
+    public get sliceBottom(): number {
+        return this._sliceBottom;
+    }
+
+    public set sliceBottom(value: number) {
+        if (this._sliceBottom === value) {
+            return;
+        }
+
+        this._sliceBottom = value;
+
+        this._markAsDirty();
+    }
+
+    /**
      * Gets or sets the left coordinate in the source image
      */
     public get sourceLeft(): number {
@@ -163,6 +258,10 @@ export class Image extends Control {
         this._imageHeight = this._domImage.height;
         this._loaded = true;
 
+        if (this._populateNinePatchSlicesFromImage) {
+            this._extractNinePatchSliceDataFromImage();
+        }
+
         if (this._autoScale) {
             this.synchronizeSizeWithContent();
         }
@@ -172,6 +271,56 @@ export class Image extends Control {
         this._markAsDirty();
     }
 
+    private _extractNinePatchSliceDataFromImage() {
+        if (!Image._WorkingCanvas) {
+            Image._WorkingCanvas = document.createElement('canvas');
+        }
+        const canvas = Image._WorkingCanvas;
+        const context = canvas.getContext('2d')!;
+        const width = this._domImage.width;
+        const height = this._domImage.height;
+
+        canvas.width = width;
+        canvas.height = height;
+
+        context.drawImage(this._domImage, 0, 0, width, height);
+        const imageData = context.getImageData(0, 0, width, height);
+
+        // Left and right
+        this._sliceLeft = -1;
+        this._sliceRight = -1;
+        for (var x = 0; x < width; x++) {
+            const alpha = imageData.data[x * 4 + 3];
+
+            if (alpha > 127 && this._sliceLeft === -1) {
+                this._sliceLeft = x;
+                continue;
+            }
+
+            if (alpha < 127 && this._sliceLeft > -1) {
+                this._sliceRight = x;
+                break;
+            }
+        }
+
+        // top and bottom
+        this._sliceTop = -1;
+        this._sliceBottom = -1;
+        for (var y = 0; y < height; y++) {
+            const alpha = imageData.data[y * width * 4 + 3];
+
+            if (alpha > 127 && this._sliceTop === -1) {
+                this._sliceTop = y;
+                continue;
+            }
+
+            if (alpha < 127 && this._sliceTop > -1) {
+                this._sliceBottom = y;
+                break;
+            }
+        }
+    }
+
     /**
      * Gets or sets image source url
      */
@@ -267,7 +416,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,44 +470,91 @@ 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;
+                case Image.STRETCH_NINE_PATCH:
+                    this._renderNinePatch(context);
+                    break;
             }
         }
+
         context.restore();
     }
 
+    private _renderCornerPatch(context: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, targetX: number, targetY: number): void {
+        context.drawImage(this._domImage, x, y, width, height, this._currentMeasure.left + targetX, this._currentMeasure.top + targetY, width, height);
+    }
+
+    private _renderNinePatch(context: CanvasRenderingContext2D): void {
+        let height = this._imageHeight;
+        let leftWidth = this._sliceLeft;
+        let topHeight = this._sliceTop;
+        let bottomHeight = this._imageHeight - this._sliceBottom;
+        let rightWidth = this._imageWidth - this._sliceRight;
+        let left = 0;
+        let top = 0;
+
+        if (this._populateNinePatchSlicesFromImage) {
+            left = 1;
+            top = 1;
+            height -= 2;
+            leftWidth -= 1;
+            topHeight -= 1;
+            bottomHeight -= 1;
+            rightWidth -= 1;
+        }
+
+        const centerWidth = this._sliceRight - this._sliceLeft + 1;
+        const targetCenterWidth = this._currentMeasure.width - rightWidth - this.sliceLeft + 1;
+        const targetTopHeight = this._currentMeasure.height - height + this._sliceBottom;
+
+        // Corners
+        this._renderCornerPatch(context, left, top, leftWidth, topHeight, 0, 0);
+        this._renderCornerPatch(context, left, this._sliceBottom, leftWidth, height - this._sliceBottom, 0, targetTopHeight);
+
+        this._renderCornerPatch(context, this._sliceRight, top, rightWidth, topHeight, this._currentMeasure.width - rightWidth, 0);
+        this._renderCornerPatch(context, this._sliceRight, this._sliceBottom, rightWidth, height - this._sliceBottom, this._currentMeasure.width - rightWidth, targetTopHeight);
+
+        // Center
+        context.drawImage(this._domImage, this._sliceLeft, this._sliceTop, centerWidth, this._sliceBottom - this._sliceTop + 1,
+            this._currentMeasure.left + leftWidth, this._currentMeasure.top + topHeight, targetCenterWidth, targetTopHeight - topHeight + 1);
+
+        // Borders
+        context.drawImage(this._domImage, left, this._sliceTop, leftWidth, this._sliceBottom - this._sliceTop,
+            this._currentMeasure.left, this._currentMeasure.top + topHeight, leftWidth, targetTopHeight - topHeight);
+
+        context.drawImage(this._domImage, this._sliceRight, this._sliceTop, leftWidth, this._sliceBottom - this._sliceTop,
+            this._currentMeasure.left + this._currentMeasure.width - rightWidth, this._currentMeasure.top + topHeight, leftWidth, targetTopHeight - topHeight);
+
+        context.drawImage(this._domImage, this._sliceLeft, top, centerWidth, topHeight,
+            this._currentMeasure.left + leftWidth, this._currentMeasure.top, targetCenterWidth, topHeight);
+
+        context.drawImage(this._domImage, this._sliceLeft, this._sliceBottom, centerWidth, bottomHeight,
+            this._currentMeasure.left + leftWidth, this._currentMeasure.top + targetTopHeight, targetCenterWidth, bottomHeight);
+    }
+
     public dispose() {
         super.dispose();
         this.onImageLoadedObservable.clear();
@@ -349,4 +569,6 @@ export class Image extends Control {
     public static readonly STRETCH_UNIFORM = 2;
     /** STRETCH_EXTEND */
     public static readonly STRETCH_EXTEND = 3;
+    /** NINE_PATCH */
+    public static readonly STRETCH_NINE_PATCH = 4;
 }

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

+ 330 - 143
gui/src/2D/controls/inputText.ts

@@ -2,7 +2,6 @@ import { Control } from "./control";
 import { IFocusableControl } from "../advancedDynamicTexture";
 import { ValueAndUnit } from "../valueAndUnit";
 import { Nullable, Observable, Observer, Vector2, ClipboardEventTypes, ClipboardInfo, PointerInfo } from 'babylonjs';
-import { Measure } from "../measure";
 import { VirtualKeyboard } from "./virtualKeyboard";
 
 /**
@@ -13,6 +12,7 @@ export class InputText extends Control implements IFocusableControl {
     private _placeholderText = "";
     private _background = "#222222";
     private _focusedBackground = "#000000";
+    private _focusedColor = "white";
     private _placeholderColor = "gray";
     private _thickness = 1;
     private _margin = new ValueAndUnit(10, ValueAndUnit.UNITMODE_PIXEL);
@@ -34,7 +34,9 @@ export class InputText extends Control implements IFocusableControl {
     private _highlightedText = "";
     private _startHighlightIndex = 0;
     private _endHighlightIndex = 0;
+    private _cursorIndex = -1;
     private _onFocusSelectAll = false;
+    private _isPointerDown = false;
     private _onClipboardObserver: Nullable<Observer<ClipboardInfo>>;
     private _onPointerDblTapObserver: Nullable<Observer<PointerInfo>>;
 
@@ -60,6 +62,8 @@ export class InputText extends Control implements IFocusableControl {
     public onTextCutObservable = new Observable<InputText>();
     /** Observable raised when paste event is triggered */
     public onTextPasteObservable = new Observable<InputText>();
+    /** Observable raised when a key event was processed */
+    public onKeyboardEventProcessedObservable = new Observable<KeyboardEvent>();
 
     /** Gets or sets the maximum width allowed by the control */
     public get maxWidth(): string | number {
@@ -182,6 +186,20 @@ export class InputText extends Control implements IFocusableControl {
         this._markAsDirty();
     }
 
+    /** Gets or sets the background color when focused */
+    public get focusedColor(): string {
+        return this._focusedColor;
+    }
+
+    public set focusedColor(value: string) {
+        if (this._focusedColor === value) {
+            return;
+        }
+
+        this._focusedColor = value;
+        this._markAsDirty();
+    }
+
     /** Gets or sets the background color */
     public get background(): string {
         return this._background;
@@ -305,6 +323,7 @@ export class InputText extends Control implements IFocusableControl {
         super(name);
 
         this.text = text;
+        this.isPointerBlocker = true;
     }
 
     /** @hidden */
@@ -481,58 +500,156 @@ export class InputText extends Control implements IFocusableControl {
                 return;
             case 13: // RETURN
                 this._host.focusedControl = null;
+                this._isTextHighlightOn = false;
                 return;
             case 35: // END
                 this._cursorOffset = 0;
                 this._blinkIsEven = false;
+                this._isTextHighlightOn = false;
                 this._markAsDirty();
                 return;
             case 36: // HOME
                 this._cursorOffset = this._text.length;
                 this._blinkIsEven = false;
+                this._isTextHighlightOn = false;
                 this._markAsDirty();
                 return;
             case 37: // LEFT
+                this._cursorOffset++;
+                if (this._cursorOffset > this._text.length) {
+                    this._cursorOffset = this._text.length;
+                }
+
                 if (evt && evt.shiftKey) {
+                    // update the cursor
+                    this._blinkIsEven = false;
+                    // shift + ctrl/cmd + <-
+                    if (evt.ctrlKey || evt.metaKey) {
+                        if (!this._isTextHighlightOn) {
+                            if (this._text.length === this._cursorOffset) {
+                                return;
+                            }
+                            else {
+                                this._endHighlightIndex = this._text.length - this._cursorOffset + 1;
+                            }
+                        }
+                        this._startHighlightIndex = 0;
+                        this._cursorIndex = this._text.length - this._endHighlightIndex;
+                        this._cursorOffset = this._text.length;
+                        this._isTextHighlightOn = true;
+                        this._markAsDirty();
+                        return;
+                    }
+                    //store the starting point
                     if (!this._isTextHighlightOn) {
                         this._isTextHighlightOn = true;
+                        this._cursorIndex = (this._cursorOffset >= this._text.length) ? this._text.length : this._cursorOffset - 1;
+                    }
+                    //if text is already highlighted
+                    else if (this._cursorIndex === -1) {
+                        this._cursorIndex = this._text.length - this._endHighlightIndex;
+                        this._cursorOffset = (this._startHighlightIndex === 0) ? this._text.length : this._text.length - this._startHighlightIndex + 1;
+                    }
+                    //set the highlight indexes
+                    if (this._cursorIndex < this._cursorOffset) {
+                        this._endHighlightIndex = this._text.length - this._cursorIndex;
+                        this._startHighlightIndex = this._text.length - this._cursorOffset;
+                    }
+                    else if (this._cursorIndex > this._cursorOffset) {
                         this._endHighlightIndex = this._text.length - this._cursorOffset;
-                        this._startHighlightIndex = this._endHighlightIndex;
+                        this._startHighlightIndex = this._text.length - this._cursorIndex;
+                    }
+                    else {
+                        this._isTextHighlightOn = false;
                     }
-                    (this._startHighlightIndex < 0) ? 0 : --this._startHighlightIndex;
+                    this._markAsDirty();
+                    return;
                 }
-                this._cursorOffset++;
-                if (this._cursorOffset > this._text.length) {
-                    this._cursorOffset = this._text.length;
+                if (this._isTextHighlightOn) {
+                    this._cursorOffset = this._text.length - this._startHighlightIndex;
+                    this._isTextHighlightOn = false;
+                }
+                if (evt && (evt.ctrlKey || evt.metaKey)) {
+                    this._cursorOffset = this.text.length;
+                    evt.preventDefault();
                 }
                 this._blinkIsEven = false;
+                this._isTextHighlightOn = false;
+                this._cursorIndex = -1;
                 this._markAsDirty();
                 return;
             case 39: // RIGHT
+                this._cursorOffset--;
+                if (this._cursorOffset < 0) {
+                    this._cursorOffset = 0;
+                }
                 if (evt && evt.shiftKey) {
+                    //update the cursor
+                    this._blinkIsEven = false;
+                    //shift + ctrl/cmd + ->
+                    if (evt.ctrlKey || evt.metaKey) {
+                        if (!this._isTextHighlightOn) {
+                            if (this._cursorOffset === 0) {
+                                return;
+                            }
+                            else {
+                                this._startHighlightIndex = this._text.length - this._cursorOffset - 1;
+                            }
+                        }
+                        this._endHighlightIndex = this._text.length;
+                        this._isTextHighlightOn = true;
+                        this._cursorIndex = this._text.length - this._startHighlightIndex;
+                        this._cursorOffset = 0;
+                        this._markAsDirty();
+                        return;
+                    }
+
                     if (!this._isTextHighlightOn) {
                         this._isTextHighlightOn = true;
+                        this._cursorIndex = (this._cursorOffset <= 0) ? 0 : this._cursorOffset + 1;
+                    }
+                    //if text is already highlighted
+                    else if (this._cursorIndex === -1) {
+                        this._cursorIndex = this._text.length - this._startHighlightIndex;
+                        this._cursorOffset = (this._text.length === this._endHighlightIndex) ? 0 : this._text.length - this._endHighlightIndex - 1;
+                    }
+                    //set the highlight indexes
+                    if (this._cursorIndex < this._cursorOffset) {
+                        this._endHighlightIndex = this._text.length - this._cursorIndex;
                         this._startHighlightIndex = this._text.length - this._cursorOffset;
-                        this._endHighlightIndex = this._startHighlightIndex;
                     }
-                    (this._endHighlightIndex > this._text.length) ? this._text.length - 1 : ++this._endHighlightIndex;
+                    else if (this._cursorIndex > this._cursorOffset) {
+                        this._endHighlightIndex = this._text.length - this._cursorOffset;
+                        this._startHighlightIndex = this._text.length - this._cursorIndex;
+                    }
+                    else {
+                        this._isTextHighlightOn = false;
+                    }
+                    this._markAsDirty();
+                    return;
                 }
-                this._cursorOffset--;
-                if (this._cursorOffset < 0) {
+                if (this._isTextHighlightOn) {
+                    this._cursorOffset = this._text.length - this._endHighlightIndex;
+                    this._isTextHighlightOn = false;
+                }
+                //ctr + ->
+                if (evt && (evt.ctrlKey || evt.metaKey)) {
                     this._cursorOffset = 0;
+                    evt.preventDefault();
                 }
                 this._blinkIsEven = false;
+                this._isTextHighlightOn = false;
+                this._cursorIndex = -1;
                 this._markAsDirty();
                 return;
             case 222: // Dead
                 if (evt) {
                     evt.preventDefault();
                 }
+                this._cursorIndex = -1;
                 this.deadKey = true;
                 break;
         }
-        this._isTextHighlightOn = false;
-
         // Printable characters
         if (key &&
             ((keyCode === -1) ||                     // Direct access
@@ -546,42 +663,78 @@ export class InputText extends Control implements IFocusableControl {
             this.onBeforeKeyAddObservable.notifyObservers(this);
             key = this._currentKey;
             if (this._addKey) {
-                if (this._cursorOffset === 0) {
+                if (this._isTextHighlightOn) {
+                    this.text = this._text.slice(0, this._startHighlightIndex) + key + this._text.slice(this._endHighlightIndex);
+                    this._cursorOffset = this.text.length - (this._startHighlightIndex + 1);
+                    this._isTextHighlightOn = false;
+                    this._blinkIsEven = false;
+                    this._markAsDirty();
+                }
+                else if (this._cursorOffset === 0) {
                     this.text += key;
                 } else {
                     let insertPosition = this._text.length - this._cursorOffset;
-
                     this.text = this._text.slice(0, insertPosition) + key + this._text.slice(insertPosition);
                 }
             }
         }
     }
+
+    /** @hidden */
+    private _updateValueFromCursorIndex(offset: number) {
+        //update the cursor
+        this._blinkIsEven = false;
+
+        if (this._cursorIndex === -1) {
+            this._cursorIndex = offset;
+        } else {
+            if (this._cursorIndex < this._cursorOffset) {
+                this._endHighlightIndex = this._text.length - this._cursorIndex;
+                this._startHighlightIndex = this._text.length - this._cursorOffset;
+            }
+            else if (this._cursorIndex > this._cursorOffset) {
+                this._endHighlightIndex = this._text.length - this._cursorOffset;
+                this._startHighlightIndex = this._text.length - this._cursorIndex;
+            }
+            else {
+                this._isTextHighlightOn = false;
+                this._markAsDirty();
+                return;
+            }
+        }
+        this._isTextHighlightOn = true;
+        this._markAsDirty();
+    }
     /** @hidden */
     private _processDblClick(evt: PointerInfo) {
         //pre-find the start and end index of the word under cursor, speeds up the rendering
         this._startHighlightIndex = this._text.length - this._cursorOffset;
         this._endHighlightIndex = this._startHighlightIndex;
-        for (let rWord = /\w+/g, left = 1, right = 1; this._startHighlightIndex > 0 && this._endHighlightIndex < this._text.length && (left || right);) {
-            right = (this._text[this._endHighlightIndex].search(rWord) !== -1) ? ++this._endHighlightIndex : 0;
-            left = (this._text[this._startHighlightIndex - 1].search(rWord) !== -1) ? --this._startHighlightIndex : 0;
-        }
+        let rWord = /\w+/g, moveLeft, moveRight;
+        do {
+            moveRight = this._endHighlightIndex < this._text.length && (this._text[this._endHighlightIndex].search(rWord) !== -1) ? ++this._endHighlightIndex : 0;
+            moveLeft = this._startHighlightIndex > 0 && (this._text[this._startHighlightIndex - 1].search(rWord) !== -1) ? --this._startHighlightIndex : 0;
+        } while (moveLeft || moveRight);
+
+        this._cursorOffset = this.text.length - this._startHighlightIndex;
         this.onTextHighlightObservable.notifyObservers(this);
+
         this._isTextHighlightOn = true;
-        this._blinkIsEven = false;
+        this._clickedCoordinate = null;
+        this._blinkIsEven = true;
+        this._cursorIndex = -1;
+        this._markAsDirty();
     }
     /** @hidden */
     private _selectAllText() {
-        this._blinkIsEven = false;
+        this._blinkIsEven = true;
         this._isTextHighlightOn = true;
 
-        //if already highlighted pass
-        if (this._highlightedText) {
-            return;
-        }
-
         this._startHighlightIndex = 0;
         this._endHighlightIndex = this._text.length;
-        this._cursorOffset = 0;
+        this._cursorOffset = this._text.length;
+        this._cursorIndex = -1;
+        this._markAsDirty();
     }
 
     /**
@@ -591,6 +744,8 @@ export class InputText extends Control implements IFocusableControl {
     public processKeyboard(evt: KeyboardEvent): void {
         // process pressed key
         this.processKey(evt.keyCode, evt.key, evt);
+
+        this.onKeyboardEventProcessedObservable.notifyObservers(evt);
     }
 
     /** @hidden */
@@ -634,161 +789,178 @@ 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";
+        }
 
-            context.fillText(text, this._scrollLeft, this._currentMeasure.top + rootY);
+        let rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+        let availableWidth = this._width.getValueInPixel(this._host, this._tempParentMeasure.width) - marginWidth;
 
-            // Cursor
-            if (this._isFocused) {
+        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;
+        }
 
-                // 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;
+        context.fillText(text, this._scrollLeft, this._currentMeasure.top + rootY);
 
-                    } while (currentSize < absoluteCursorPosition && (text.length >= this._cursorOffset));
+        // Cursor
+        if (this._isFocused) {
 
-                    // 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));
+
+                // Find closest move
+                if (Math.abs(absoluteCursorPosition - currentSize) > previousDist) {
+                    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;
+                this._blinkIsEven = false;
+                this._clickedCoordinate = null;
+            }
+
+            // 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();
-                    }
+                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();
+                }
+                if (!this._isTextHighlightOn) {
                     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);
+            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;
+            //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);
+                let width = context.measureText(this.text.substring(this._startHighlightIndex, this._endHighlightIndex)).width;
+                if (highlightCursorLeft < clipTextLeft) {
+                    width = width - (clipTextLeft - highlightCursorLeft);
+                    if (!width) {
+                        // when using left arrow on text.length > availableWidth;
+                        // assigns the width of the first letter after clipTextLeft
+                        width = context.measureText(this.text.charAt(this.text.length - this._cursorOffset)).width;
+                    }
+                    highlightCursorLeft = clipTextLeft;
                 }
+                //for transparancy
+                context.globalAlpha = this._highligherOpacity;
+                context.fillStyle = this._textHighlightColor;
+                context.fillRect(highlightCursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, width, this._fontOffset.height);
+                context.globalAlpha = 1.0;
             }
+        }
+        context.restore();
 
-            context.restore();
-
-            // Border
-            if (this._thickness) {
+        // Border
+        if (this._thickness) {
+            if (this._isFocused) {
+                if (this.focusedColor) {
+                    context.strokeStyle = this.focusedColor;
+                }
+            } else {
                 if (this.color) {
                     context.strokeStyle = this.color;
                 }
-                context.lineWidth = this._thickness;
-
-                context.strokeRect(this._currentMeasure.left + this._thickness / 2, this._currentMeasure.top + this._thickness / 2,
-                    this._currentMeasure.width - this._thickness, this._currentMeasure.height - this._thickness);
             }
+
+            context.lineWidth = this._thickness;
+
+            context.strokeRect(this._currentMeasure.left + this._thickness / 2, this._currentMeasure.top + this._thickness / 2,
+                this._currentMeasure.width - this._thickness, this._currentMeasure.height - this._thickness);
         }
+
         context.restore();
     }
 
@@ -800,6 +972,9 @@ export class InputText extends Control implements IFocusableControl {
         this._clickedCoordinate = coordinates.x;
         this._isTextHighlightOn = false;
         this._highlightedText = "";
+        this._cursorIndex = -1;
+        this._isPointerDown = true;
+        this._host._capturingControl[pointerId] = this;
         if (this._host.focusedControl === this) {
             // Move cursor
             clearTimeout(this._blinkTimeout);
@@ -813,8 +988,19 @@ export class InputText extends Control implements IFocusableControl {
 
         return true;
     }
+    public _onPointerMove(target: Control, coordinates: Vector2): void {
+        if (this._host.focusedControl === this && this._isPointerDown) {
+            this._clickedCoordinate = coordinates.x;
+            this._markAsDirty();
+            this._updateValueFromCursorIndex(this._cursorOffset);
+        }
+        super._onPointerMove(target, coordinates);
+    }
 
     public _onPointerUp(target: Control, coordinates: Vector2, pointerId: number, buttonIndex: number, notifyClick: boolean): void {
+
+        this._isPointerDown = false;
+        delete this._host._capturingControl[pointerId];
         super._onPointerUp(target, coordinates, pointerId, buttonIndex, notifyClick);
     }
 
@@ -832,5 +1018,6 @@ export class InputText extends Control implements IFocusableControl {
         this.onTextCutObservable.clear();
         this.onTextPasteObservable.clear();
         this.onTextHighlightObservable.clear();
+        this.onKeyboardEventProcessedObservable.clear();
     }
 }

+ 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 - 364
gui/src/2D/controls/scrollViewer.ts

@@ -1,364 +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";
-
-/**
- * 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;
-
-    /**
-     * 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._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 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);
-    }
-
-    /** @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);
-        });
-    }
-}

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

@@ -0,0 +1,386 @@
+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 _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;
+
+    /**
+     * Gets the horizontal scrollbar
+     */
+    public get horizontalBar(): ScrollBar {
+        return this._horizontalBar;
+    }
+
+    /**
+     * Gets the vertical scrollbar
+     */
+    public get verticalBar(): ScrollBar {
+        return this._verticalBar;
+    }
+
+    /**
+     * 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 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;
+
+        const newLeft = this._horizontalBar.value * this._endLeft + "px";
+        const newTop = this._verticalBar.value * this._endTop + "px";
+
+        if (newLeft !== this._window.left) {
+            this._window.left = newLeft;
+            this._rebuildLayout = true;
+        }
+
+        if (newTop !== this._window.top) {
+            this._window.top = newTop;
+            this._rebuildLayout = true;
+        }
+
+        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();

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

@@ -0,0 +1,147 @@
+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;
+
+        const thumbPosition = this._getThumbPosition();
+        context.fillStyle = this._background;
+
+        context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.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();
+    }
+}

+ 51 - 30
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,39 +81,65 @@ 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";
-                if (!child._top.ignoreAdaptiveScaling) {
-                    child._markAsDirty();
+                if (child.top !== stackHeight + "px") {
+                    child.top = stackHeight + "px";
+                    this._rebuildLayout = true;
+                    child._top.ignoreAdaptiveScaling = true;
                 }
-                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();
+                if (child.left !== stackWidth + "px") {
+                    child.left = stackWidth + "px";
+                    this._rebuildLayout = true;
+                    child._left.ignoreAdaptiveScaling = true;
                 }
-                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 +150,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 +172,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);
+        }
+
+        super._processMeasures(parentMeasure, context);
+
+        // 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._rebuildLayout = 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._rebuildLayout = true;
+            }
+        }
+    }
+
     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';
         }
     }
 

+ 1 - 0
gui/src/2D/index.ts

@@ -1,6 +1,7 @@
 export * from "./controls";
 
 export * from "./advancedDynamicTexture";
+export * from "./adtInstrumentation";
 export * from "./math2D";
 export * from "./measure";
 export * from "./multiLinePoint";

+ 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

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

@@ -39,10 +39,10 @@ export class CylinderPanel extends VolumeBasedPanel {
 
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
-                mesh.lookAt(new BABYLON.Vector3(-newPos.x, newPos.y, -newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, newPos.y, 2 * newPos.z));
                 break;
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, newPos.y, 2 * newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(-newPos.x, newPos.y, -newPos.z));
                 break;
             case Container3D.FACEFORWARD_ORIENTATION:
                 break;

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

@@ -22,12 +22,12 @@ export class PlanePanel extends VolumeBasedPanel {
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
             case Container3D.FACEFORWARD_ORIENTATION:
-                target.addInPlace(new BABYLON.Vector3(0, 0, -1));
+                target.addInPlace(new BABYLON.Vector3(0, 0, 1));
                 mesh.lookAt(target);
                 break;
             case Container3D.FACEFORWARDREVERSED_ORIENTATION:
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                target.addInPlace(new BABYLON.Vector3(0, 0, 1));
+                target.addInPlace(new BABYLON.Vector3(0, 0, -1));
                 mesh.lookAt(target);
                 break;
         }

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

@@ -39,11 +39,11 @@ export class ScatterPanel extends VolumeBasedPanel {
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
             case Container3D.FACEFORWARD_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, -1));
+                mesh.lookAt(new Vector3(0, 0, 1));
                 break;
             case Container3D.FACEFORWARDREVERSED_ORIENTATION:
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                mesh.lookAt(new Vector3(0, 0, 1));
+                mesh.lookAt(new Vector3(0, 0, -1));
                 break;
         }
 

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

@@ -40,10 +40,10 @@ export class SpherePanel extends VolumeBasedPanel {
 
         switch (this.orientation) {
             case Container3D.FACEORIGIN_ORIENTATION:
-                mesh.lookAt(new BABYLON.Vector3(-newPos.x, -newPos.y, -newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, 2 * newPos.y, 2 * newPos.z));
                 break;
             case Container3D.FACEORIGINREVERSED_ORIENTATION:
-                mesh.lookAt(new BABYLON.Vector3(2 * newPos.x, 2 * newPos.y, 2 * newPos.z));
+                mesh.lookAt(new BABYLON.Vector3(-newPos.x, -newPos.y, -newPos.z));
                 break;
             case Container3D.FACEFORWARD_ORIENTATION:
                 break;

+ 75 - 28
inspector/src/components/actionTabs/actionTabs.scss

@@ -144,6 +144,10 @@
                 -moz-user-select: none;   
                 -ms-user-select: none;    
                 user-select: none;     
+
+                .underline {
+                    border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
+                }
                 
                 .textureLinkLine {
                     display: grid;
@@ -231,6 +235,10 @@
                         margin:5px;
                         margin-top: 6px;
                         max-width: 200px;
+                        -webkit-user-select: text; 
+                        -moz-user-select: text;   
+                        -ms-user-select: text;    
+                        user-select: text;                
 
                         &.check {
                             color: green;
@@ -322,7 +330,7 @@
                     padding-left: 5px;
                     height: 30px;
                     display: grid;
-                    grid-template-columns: 1fr 30px;
+                    grid-template-columns: 1fr 24px;
 
                     .label {
                         grid-column: 1;
@@ -677,41 +685,80 @@
                 
                 .color3Line {
                     padding-left: 5px;
-                    height: 30px;
                     display: grid;
-                    grid-template-columns: 1fr auto;
-
 
-                    .label {
-                        grid-column: 1;
-                        display: flex;
-                        align-items: center;
-                    }
+                    .firstLine {
+                        height: 30px;
+                        display: grid;
+                        grid-template-columns: 1fr auto 20px;
 
-                    .color3 {
-                        grid-column: 2;
-                        
-                        display: flex;
-                        align-items: center;   
+                        .label {
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;
+                        }
 
-                        input[type="color"] {
-                            -webkit-appearance: none;
-                            border: none;
-                            padding: 0;
-                            width: 20px;
-                            height: 20px;
+                        .color3 {
+                            grid-column: 2;
+                            
+                            display: flex;
+                            align-items: center;   
+
+                            input[type="color"] {
+                                -webkit-appearance: none;
+                                border: none;
+                                padding: 0;
+                                width: 30px;
+                                height: 20px;
+                            }
+                            input[type="color"]::-webkit-color-swatch-wrapper {
+                                padding: 0;
+                            }
+                            input[type="color"]::-webkit-color-swatch {
+                                border: none;
+                            }
+                            
+                            input {
+                                margin-right: 5px;
+                            }
                         }
-                        input[type="color"]::-webkit-color-swatch-wrapper {
-                            padding: 0;
+
+                        .expand {
+                            grid-column: 3;
+                            display: grid;
+                            align-items: center;
+                            justify-items: center;
+                            cursor: pointer;
                         }
-                        input[type="color"]::-webkit-color-swatch {
-                            border: none;
+                    }   
+
+                    .secondLine {
+                        display: grid;
+                        padding-right: 5px;  
+                        border-left: 1px solid rgb(51, 122, 183);
+
+                        .numeric {
+                            display: grid;
+                            grid-template-columns: 1fr auto;
                         }
-                        
-                        input {
-                            margin-right: 5px;
+
+                        .numeric-label {
+                            text-align: right;
+                            grid-column: 1;
+                            display: flex;
+                            align-items: center;                            
+                            justify-self: right;
+                            margin-right: 10px;                          
                         }
-                    }                    
+
+                        .numeric-value {
+                            width: 120px;
+                            grid-column: 2;
+                            display: flex;
+                            align-items: center;  
+                            border: 1px solid  rgb(51, 122, 183);
+                        }                        
+                    }                  
                 }     
                 
                 .listLine {

+ 81 - 7
inspector/src/components/actionTabs/lines/color3LineComponent.tsx

@@ -1,6 +1,9 @@
 import * as React from "react";
 import { Observable, Color3 } from "babylonjs";
 import { PropertyChangedEvent } from "../../propertyChangedEvent";
+import { NumericInputComponent } from "./numericInputComponent";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
 
 export interface IColor3LineComponentProps {
     label: string,
@@ -9,12 +12,12 @@ export interface IColor3LineComponentProps {
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>
 }
 
-export class Color3LineComponent extends React.Component<IColor3LineComponentProps, { color: Color3 }> {
+export class Color3LineComponent extends React.Component<IColor3LineComponentProps, { isExpanded: boolean, color: Color3 }> {
     private _localChange = false;
     constructor(props: IColor3LineComponentProps) {
         super(props);
 
-        this.state = { color: this.props.target[this.props.propertyName].clone() };
+        this.state = { isExpanded: false, color: this.props.target[this.props.propertyName].clone() };
     }
 
     shouldComponentUpdate(nextProps: IColor3LineComponentProps, nextState: { color: Color3 }) {
@@ -46,15 +49,86 @@ export class Color3LineComponent extends React.Component<IColor3LineComponentPro
         this.setState({ color: newColor });
     }
 
+    switchExpandState() {
+        this._localChange = true;
+        this.setState({ isExpanded: !this.state.isExpanded });
+    }
+
+    raiseOnPropertyChanged(previousValue: Color3) {
+
+        if (!this.props.onPropertyChangedObservable) {
+            return;
+        }
+        this.props.onPropertyChangedObservable.notifyObservers({
+            object: this.props.target,
+            property: this.props.propertyName,
+            value: this.state.color,
+            initialValue: previousValue
+        });
+    }
+
+    updateStateR(value: number) {
+        this._localChange = true;
+
+        const store = this.state.color.clone();
+        this.props.target[this.props.propertyName].x = value;
+        this.state.color.r = value;
+        this.props.target[this.props.propertyName] = this.state.color;
+        this.setState({ color: this.state.color });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateG(value: number) {
+        this._localChange = true;
+
+        const store = this.state.color.clone();
+        this.props.target[this.props.propertyName].g = value;
+        this.state.color.g = value;
+        this.props.target[this.props.propertyName] = this.state.color;
+        this.setState({ color: this.state.color });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
+    updateStateB(value: number) {
+        this._localChange = true;
+
+        const store = this.state.color.clone();
+        this.props.target[this.props.propertyName].b = value;
+        this.state.color.b = value;
+        this.props.target[this.props.propertyName] = this.state.color;
+        this.setState({ color: this.state.color });
+
+        this.raiseOnPropertyChanged(store);
+    }
+
     render() {
+
+        const chevron = this.state.isExpanded ? <FontAwesomeIcon icon={faMinus} /> : <FontAwesomeIcon icon={faPlus} />
+        const colorAsColor3 = this.state.color.getClassName() === "Color3" ? this.state.color : new BABYLON.Color3(this.state.color.r, this.state.color.g, this.state.color.b);
+
         return (
             <div className="color3Line">
-                <div className="label">
-                    {this.props.label}
-                </div>
-                <div className="color3">
-                    <input type="color" value={this.state.color.toHexString()} onChange={(evt) => this.onChange(evt.target.value)} />
+                <div className="firstLine">
+                    <div className="label">
+                        {this.props.label}
+                    </div>
+                    <div className="color3">
+                        <input type="color" value={colorAsColor3.toHexString()} onChange={(evt) => this.onChange(evt.target.value)} />
+                    </div>
+                    <div className="expand" onClick={() => this.switchExpandState()}>
+                        {chevron}
+                    </div>
                 </div>
+                {
+                    this.state.isExpanded &&
+                    <div className="secondLine">
+                        <NumericInputComponent label="r" value={this.state.color.r} onChange={value => this.updateStateR(value)} />
+                        <NumericInputComponent label="g" value={this.state.color.g} onChange={value => this.updateStateG(value)} />
+                        <NumericInputComponent label="b" value={this.state.color.b} onChange={value => this.updateStateB(value)} />
+                    </div>
+                }
             </div>
         );
     }

+ 1 - 1
inspector/src/components/actionTabs/lines/floatLineComponent.tsx

@@ -109,7 +109,7 @@ export class FloatLineComponent extends React.Component<IFloatLineComponentProps
                     {this.props.label}
                 </div>
                 <div className="value">
-                    <input className="numeric-input" value={this.state.value} onBlur={() => this.unlock()} onFocus={() => this.lock()} onChange={evt => this.updateValue(evt.target.value)} />
+                    <input type="number" step="0.01" className="numeric-input" value={this.state.value} onBlur={() => this.unlock()} onFocus={() => this.lock()} onChange={evt => this.updateValue(evt.target.value)} />
                 </div>
             </div>
         );

+ 1 - 1
inspector/src/components/actionTabs/lines/numericInputComponent.tsx

@@ -56,7 +56,7 @@ export class NumericInputComponent extends React.Component<INumericInputComponen
                         {`${this.props.label}: `}
                     </div>
                 }
-                <input className="numeric-input" value={this.state.value} onChange={evt => this.updateValue(evt)} />
+                <input type="number" step="1" className="numeric-input" value={this.state.value} onChange={evt => this.updateValue(evt)} />
             </div>
         )
     }

+ 2 - 1
inspector/src/components/actionTabs/lines/textLineComponent.tsx

@@ -4,6 +4,7 @@ interface ITextLineComponentProps {
     label: string,
     value: string,
     color?: string,
+    underline?: boolean,
     onLink?: () => void
 }
 
@@ -37,7 +38,7 @@ export class TextLineComponent extends React.Component<ITextLineComponentProps>
 
     render() {
         return (
-            <div className="textLine">
+            <div className={this.props.underline ? "textLine underline" : "textLine"}>
                 <div className="label">
                     {this.props.label}
                 </div>

+ 2 - 2
inspector/src/components/actionTabs/tabs/debugTabComponent.tsx

@@ -2,7 +2,7 @@ import * as React from "react";
 import { PaneComponent, IPaneComponentProps } from "../paneComponent";
 import { LineContainerComponent } from "../lineContainerComponent";
 import { CheckBoxLineComponent } from "../lines/checkBoxLineComponent";
-import { GridPropertyGridComponent } from "./propertyGrids/gridPropertyGridComponent";
+import { RenderGridPropertyGridComponent } from "./propertyGrids/renderGridPropertyGridComponent";
 
 export class DebugTabComponent extends PaneComponent {
     private _skeletonViewersEnabled = false;
@@ -107,7 +107,7 @@ export class DebugTabComponent extends PaneComponent {
         return (
             <div className="pane">
                 <LineContainerComponent title="HELPERS">
-                    <GridPropertyGridComponent scene={scene} />
+                    <RenderGridPropertyGridComponent scene={scene} />
                     <CheckBoxLineComponent label="Bones" isSelected={() => this._skeletonViewersEnabled} onSelect={() => this.switchSkeletonViewers()} />
                     <CheckBoxLineComponent label="Physics" isSelected={() => this._physicsViewersEnabled} onSelect={() => this.switchPhysicsViewers()} />
                 </LineContainerComponent>

+ 17 - 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, Grid } from "babylonjs-gui";
 import { ColorPickerPropertyGridComponent } from "./propertyGrids/gui/colorPickerPropertyGridComponent";
 import { AnimationGroupGridComponent } from "./propertyGrids/animationGroupPropertyGridComponent";
 import { LockObject } from "./propertyGrids/lockObject";
@@ -31,6 +31,8 @@ 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";
+import { GridPropertyGridComponent } from "./propertyGrids/gui/gridPropertyGridComponent";
 
 export class PropertyGridTabComponent extends PaneComponent {
     private _timerIntervalId: number;
@@ -214,6 +216,20 @@ export class PropertyGridTabComponent extends PaneComponent {
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
             }
 
+            if (className === "Grid") {
+                const grid = entity as Grid;
+                return (<GridPropertyGridComponent grid={grid}
+                    lockObject={this._lockObject}
+                    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}

+ 2 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx

@@ -75,9 +75,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
         this.connect(this.props.animationGroup);
 
         this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(() => {
-            if (this.props.animationGroup.isPlaying) {
-                this.updateCurrentFrame(this.props.animationGroup);
-            }
+            this.updateCurrentFrame(this.props.animationGroup);
         });
     }
 
@@ -127,7 +125,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
                 <LineContainerComponent title="CONTROLS">
                     <ButtonLineComponent label={playButtonText} onClick={() => this.playOrPause()} />
                     <SliderLineComponent label="Speed ratio" minimum={0} maximum={10} step={0.1} target={animationGroup} propertyName="speedRatio" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                    <SliderLineComponent ref="timeline" label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 100.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
+                    <SliderLineComponent ref="timeline" label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 1000.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
                 </LineContainerComponent>
                 <LineContainerComponent title="INFOS">
                     <TextLineComponent label="Animation count" value={animationGroup.targetedAnimations.length.toString()} />

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

@@ -9,6 +9,7 @@ import { FloatLineComponent } from "../../../lines/floatLineComponent";
 import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
 import { LockObject } from "../lockObject";
 import { OptionsLineComponent } from "../../../lines/optionsLineComponent";
+import { Grid } from "babylonjs-gui";
 
 interface ICommonControlPropertyGridComponentProps {
     control: Control,
@@ -21,6 +22,30 @@ export class CommonControlPropertyGridComponent extends React.Component<ICommonC
         super(props);
     }
 
+    renderGridInformation() {
+        const control = this.props.control;
+
+        if (!control.parent || !control.parent.parent) {
+            return null;
+        }
+
+        const gridParent = control.parent.parent;
+
+        if ((gridParent as any).rowCount === undefined) {
+            return null;
+        }
+
+        const grid = gridParent as Grid;
+        const cellInfos = grid.getChildCellInfo(control).split(":");
+
+        return (
+            <LineContainerComponent title="GRID">
+                <TextLineComponent label={"Row"} value={cellInfos[0]} />
+                <TextLineComponent label={"Column"} value={cellInfos[1]} />
+            </LineContainerComponent>
+        );
+    }
+
     render() {
         const control = this.props.control;
 
@@ -42,7 +67,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} />
                     }
                     {
@@ -50,6 +75,9 @@ export class CommonControlPropertyGridComponent extends React.Component<ICommonC
                         <TextInputLineComponent lockObject={this.props.lockObject} label="Background" target={control} propertyName="background" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     }
                 </LineContainerComponent>
+                {
+                    this.renderGridInformation()
+                }
                 <LineContainerComponent title="ALIGNMENT">
                     <OptionsLineComponent label="Horizontal" options={horizontalOptions} target={control} propertyName="horizontalAlignment" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <OptionsLineComponent label="Vertical" options={verticalOptions} target={control} propertyName="verticalAlignment" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

+ 80 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/gui/gridPropertyGridComponent.tsx

@@ -0,0 +1,80 @@
+import * as React from "react";
+import { Observable } from "babylonjs";
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { CommonControlPropertyGridComponent } from "./commonControlPropertyGridComponent";
+import { LockObject } from "../lockObject";
+import { Grid } from "babylonjs-gui/2D/controls/grid";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+
+interface IGridPropertyGridComponentProps {
+    grid: Grid,
+    lockObject: LockObject,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class GridPropertyGridComponent extends React.Component<IGridPropertyGridComponentProps> {
+    constructor(props: IGridPropertyGridComponentProps) {
+        super(props);
+    }
+
+    renderRows() {
+        const grid = this.props.grid;
+        const rows = [];
+
+        for (var index = 0; index < grid.rowCount; index++) {
+            rows.push(grid.getRowDefinition(index)!);
+        }
+
+        return (
+            rows.map((rd, i) => {
+                return (
+                    <TextLineComponent key={`r${i}`} label={`Row ${i}`} value={rd.toString(grid.host)} underline={i === grid.rowCount - 1} />
+                )
+            })
+        );
+    }
+
+    renderColumns() {
+        const grid = this.props.grid;
+        const cols = [];
+
+        for (var index = 0; index < grid.columnCount; index++) {
+            cols.push(grid.getColumnDefinition(index)!);
+        }
+
+        return (
+            cols.map((cd, i) => {
+                return (
+                    <TextLineComponent key={`c${i}`} label={`Column ${i}`} value={cd.toString(grid.host)} />
+                )
+            })
+        );
+    }
+
+    render() {
+        const grid = this.props.grid;
+
+        const cols = [];
+
+
+
+        for (var index = 0; index < grid.rowCount; index++) {
+            cols.push(grid.getColumnDefinition(index));
+        }
+
+        return (
+            <div className="pane">
+                <CommonControlPropertyGridComponent lockObject={this.props.lockObject} control={grid} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="GRID">
+                    {
+                        this.renderRows()
+                    }
+                    {
+                        this.renderColumns()
+                    }
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

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

@@ -27,7 +27,8 @@ export class ImagePropertyGridComponent extends React.Component<IImagePropertyGr
             { label: "None", value: BABYLON.GUI.Image.STRETCH_NONE },
             { label: "Fill", value: BABYLON.GUI.Image.STRETCH_FILL },
             { label: "Uniform", value: BABYLON.GUI.Image.STRETCH_UNIFORM },
-            { label: "Extend", value: BABYLON.GUI.Image.STRETCH_EXTEND }
+            { label: "Extend", value: BABYLON.GUI.Image.STRETCH_EXTEND },
+            { label: "NinePatch", value: BABYLON.GUI.Image.STRETCH_NINE_PATCH }
         ];
 
         return (

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

@@ -0,0 +1,41 @@
+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 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>
+        );
+    }
+}

+ 0 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx


Някои файлове не бяха показани, защото твърде много файлове са промени