فهرست منبع

Merge pull request #5604 from BabylonJS/master

merge master
David Catuhe 6 سال پیش
والد
کامیت
c33067676d
100فایلهای تغییر یافته به همراه16104 افزوده شده و 10700 حذف شده
  1. 1 1
      .vscode/tasks.json
  2. 1445 1041
      Playground/babylon.d.txt
  3. 1 0
      Playground/debug.html
  4. 1 0
      Playground/frame.html
  5. 1 0
      Playground/full.html
  6. 2 0
      Playground/index-local.html
  7. 1 0
      Playground/index.html
  8. 1 0
      Playground/indexStable.html
  9. 1 0
      Playground/ts.html
  10. 1 0
      Playground/zipContent/index.html
  11. 1 0
      Tools/Gulp/config.json
  12. 27 18
      Tools/Gulp/gulpfile.js
  13. 7 7
      Tools/Gulp/package.json
  14. 2 1
      Viewer/tests/validation/validate.html
  15. 666 0
      dist/preview release/ammo.js
  16. 1175 871
      dist/preview release/babylon.d.ts
  17. 1 1
      dist/preview release/babylon.js
  18. 923 146
      dist/preview release/babylon.max.js
  19. 923 146
      dist/preview release/babylon.no-module.max.js
  20. 1 1
      dist/preview release/babylon.worker.js
  21. 925 148
      dist/preview release/es6.js
  22. 1 1
      dist/preview release/glTF2Interface/package.json
  23. 142 27
      dist/preview release/gui/babylon.gui.d.ts
  24. 1 1
      dist/preview release/gui/babylon.gui.js
  25. 1 1
      dist/preview release/gui/babylon.gui.min.js
  26. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  27. 6147 5914
      dist/preview release/gui/babylon.gui.module.d.ts
  28. 2 2
      dist/preview release/gui/package.json
  29. 12 12
      dist/preview release/inspector/babylon.inspector.bundle.js
  30. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  31. 5 5
      dist/preview release/inspector/package.json
  32. 11 6
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  33. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  34. 11 6
      dist/preview release/loaders/babylon.glTFFileLoader.js
  35. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  36. 11 6
      dist/preview release/loaders/babylonjs.loaders.js
  37. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  38. 3 3
      dist/preview release/loaders/package.json
  39. 2 2
      dist/preview release/materialsLibrary/package.json
  40. 2 2
      dist/preview release/postProcessesLibrary/package.json
  41. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  42. 3 3
      dist/preview release/serializers/package.json
  43. 1 15
      dist/preview release/viewer/babylon.viewer.d.ts
  44. 1 1
      dist/preview release/viewer/babylon.viewer.js
  45. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  46. 1 18
      dist/preview release/viewer/babylon.viewer.module.d.ts
  47. 21 2
      dist/preview release/what's new.md
  48. 7 11
      gui/src/2D/advancedDynamicTexture.ts
  49. 6 1
      gui/src/2D/controls/button.ts
  50. 32 34
      gui/src/2D/controls/checkbox.ts
  51. 1527 1479
      gui/src/2D/controls/colorpicker.ts
  52. 86 54
      gui/src/2D/controls/container.ts
  53. 221 76
      gui/src/2D/controls/control.ts
  54. 2 3
      gui/src/2D/controls/displayGrid.ts
  55. 46 5
      gui/src/2D/controls/grid.ts
  56. 51 35
      gui/src/2D/controls/image.ts
  57. 4 3
      gui/src/2D/controls/index.ts
  58. 297 135
      gui/src/2D/controls/inputText.ts
  59. 9 11
      gui/src/2D/controls/line.ts
  60. 19 21
      gui/src/2D/controls/multiLine.ts
  61. 31 35
      gui/src/2D/controls/radioButton.ts
  62. 359 0
      gui/src/2D/controls/scrollViewers/scrollViewer.ts
  63. 74 0
      gui/src/2D/controls/scrollViewers/scrollViewerWindow.ts
  64. 1 1
      gui/src/2D/controls/selector.ts
  65. 0 227
      gui/src/2D/controls/slider.ts
  66. 4 3
      gui/src/2D/controls/baseSlider.ts
  67. 46 44
      gui/src/2D/controls/imageBasedSlider.ts
  68. 147 0
      gui/src/2D/controls/sliders/scrollBar.ts
  69. 240 0
      gui/src/2D/controls/sliders/slider.ts
  70. 51 30
      gui/src/2D/controls/stackPanel.ts
  71. 49 25
      gui/src/2D/controls/textBlock.ts
  72. 13 0
      gui/src/2D/valueAndUnit.ts
  73. 2 2
      gui/src/3D/controls/cylinderPanel.ts
  74. 2 2
      gui/src/3D/controls/planePanel.ts
  75. 2 2
      gui/src/3D/controls/scatterPanel.ts
  76. 2 2
      gui/src/3D/controls/spherePanel.ts
  77. 12 0
      inspector/src/components/actionTabs/actionTabs.scss
  78. 3 2
      inspector/src/components/actionTabs/actionTabsComponent.tsx
  79. 31 6
      inspector/src/components/actionTabs/lineContainerComponent.tsx
  80. 1 1
      inspector/src/components/actionTabs/lines/optionsLineComponent.tsx
  81. 2 1
      inspector/src/components/actionTabs/lines/textLineComponent.tsx
  82. 2 2
      inspector/src/components/actionTabs/tabs/debugTabComponent.tsx
  83. 17 1
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  84. 2 4
      inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx
  85. 36 2
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx
  86. 80 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/gridPropertyGridComponent.tsx
  87. 41 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/scrollViewerPropertyGridComponent.tsx
  88. 15 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/textBlockPropertyGridComponent.tsx
  89. 8 10
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx
  90. 3 3
      inspector/src/components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx
  91. 3 3
      inspector/src/components/actionTabs/tabs/propertyGrids/gridPropertyGridComponent.tsx
  92. 1 0
      inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx
  93. 4 0
      inspector/src/components/embedHost/embedHost.scss
  94. 4 2
      inspector/src/components/embedHost/embedHostComponent.tsx
  95. 2 1
      inspector/src/components/headerComponent.tsx
  96. 2 2
      inspector/src/components/sceneExplorer/entities/cameraTreeItemComponent.tsx
  97. 5 0
      inspector/src/components/sceneExplorer/entities/gui/advancedDynamicTextureTreeItemComponent.tsx
  98. 4 0
      inspector/src/components/sceneExplorer/sceneExplorer.scss
  99. 3 2
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  100. 0 0
      inspector/src/inspector.ts

+ 1 - 1
.vscode/tasks.json

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

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1445 - 1041
Playground/babylon.d.txt


+ 1 - 0
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>

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

+ 2 - 0
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>

+ 1 - 0
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>

+ 1 - 0
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>

+ 1 - 0
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>

+ 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"
             ],

+ 27 - 18
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");
@@ -793,21 +793,27 @@ gulp.task("watch", gulp.series("srcTscWatch", function startWatch() {
                 //config.stats = "minimal";
                 tasks.push(webpackStream(wpconfig, webpack).pipe(gulp.dest(outputDirectory)))
             } else {
-                tasks.push(gulp.watch(library.files, { interval: interval }, function() {
-                    console.log(library.output);
-                    return buildExternalLibrary(library, config[module], true)
-                        .pipe(debug());
-                }));
-                tasks.push(gulp.watch(library.shaderFiles, { interval: interval }, function() {
-                    console.log(library.output);
-                    return buildExternalLibrary(library, config[module], true)
-                        .pipe(debug())
-                }));
-                tasks.push(gulp.watch(library.sassFiles, { interval: interval }, function() {
-                    console.log(library.output);
-                    return buildExternalLibrary(library, config[module], true)
-                        .pipe(debug())
-                }));
+                if (library.files) {
+                    tasks.push(gulp.watch(library.files, { interval: interval }, function() {
+                        console.log(library.output);
+                        return buildExternalLibrary(library, config[module], true)
+                            .pipe(debug());
+                    }));
+                }
+                if (library.shaderFiles) {
+                    tasks.push(gulp.watch(library.shaderFiles, { interval: interval }, function() {
+                        console.log(library.output);
+                        return buildExternalLibrary(library, config[module], true)
+                            .pipe(debug())
+                    }));
+                }
+                if (library.sassFiles) {
+                    tasks.push(gulp.watch(library.sassFiles, { interval: interval }, function() {
+                        console.log(library.output);
+                        return buildExternalLibrary(library, config[module], true)
+                            .pipe(debug())
+                    }));
+                }
             }
         });
     });
@@ -841,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>

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


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


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


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


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


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


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 925 - 148
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.8",
+    "version": "4.0.0-alpha.10",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 142 - 27
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
                 */
@@ -516,6 +515,13 @@ declare module BABYLON.GUI {
                 */
             getValueInPixel(host: AdvancedDynamicTexture, refValue: number): number;
             /**
+                * Update the current value and unit. This should be done cautiously as the GUi won't be marked as dirty with this function.
+                * @param value defines the value to store
+                * @param unit defines the unit to store
+                * @returns the current ValueAndUnit
+                */
+            updateInPlace(value: number, unit?: number): ValueAndUnit;
+            /**
                 * Gets the value accordingly to its unit
                 * @param host  defines the root host
                 * @returns the value
@@ -724,7 +730,7 @@ declare module BABYLON.GUI {
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
             /** @hidden */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _onPointerDown(target: Control, coordinates: BABYLON.Vector2, pointerId: number, buttonIndex: number): boolean;
             /**
@@ -746,7 +752,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 +768,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;
@@ -853,20 +864,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 */
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            protected _beforeLayout(): void;
+            /** @hidden */
+            _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean;
+            protected _postMeasure(): void;
+            /** @hidden */
+            _draw(context: CanvasRenderingContext2D): void;
             /** @hidden */
             _getDescendants(results: Control[], directDescendantsOnly?: boolean, predicate?: (control: Control) => boolean): void;
             /** @hidden */
             _processPicking(x: number, y: number, type: number, pointerId: number, buttonIndex: number): boolean;
             /** @hidden */
-            protected _clipForChildren(context: CanvasRenderingContext2D): void;
-            /** @hidden */
             protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
             /** Releases associated resources */
             dispose(): void;
@@ -885,8 +903,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>;
@@ -907,6 +923,8 @@ declare module BABYLON.GUI {
             /** @hidden */
             protected _verticalAlignment: number;
             /** @hidden */
+            protected _isDirty: boolean;
+            /** @hidden */
             _tempParentMeasure: Measure;
             /** @hidden */
             protected _cachedParentMeasure: Measure;
@@ -923,6 +941,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
@@ -997,6 +1019,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;
@@ -1187,6 +1213,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;
             /**
@@ -1238,6 +1270,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;
@@ -1246,19 +1282,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 */
@@ -1268,7 +1304,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
@@ -1364,6 +1404,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)
@@ -1401,6 +1453,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
@@ -1435,7 +1493,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;
     }
@@ -1509,7 +1567,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;
@@ -1608,8 +1667,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;
@@ -1651,7 +1711,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;
             /**
@@ -1726,7 +1786,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;
@@ -1757,7 +1817,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
@@ -1794,7 +1854,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 {
@@ -1973,6 +2036,53 @@ declare module BABYLON.GUI {
 }
 declare module BABYLON.GUI {
     /**
+        * Class used to hold a viewer window and sliders in a grid
+     */
+    export class ScrollViewer extends Rectangle {
+            /**
+                * Adds a new control to the current container
+                * @param control defines the control to add
+                * @returns the current container
+                */
+            addControl(control: BABYLON.Nullable<Control>): Container;
+            /**
+                * Removes a control from the current container
+                * @param control defines the control to remove
+                * @returns the current container
+                */
+            removeControl(control: Control): Container;
+            /** Gets the list of children */
+            readonly children: Control[];
+            _flagDescendantsAsMatrixDirty(): void;
+            /**
+             * Creates a new ScrollViewer
+             * @param name of ScrollViewer
+             */
+            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 size of the bar */
+            barSize: number;
+            /** Gets or sets the bar background */
+            barBackground: string;
+            _link(host: AdvancedDynamicTexture): void;
+            _renderHighlightSpecific(context: CanvasRenderingContext2D): void;
+            /** Releases associated resources */
+            dispose(): void;
+    }
+}
+declare module BABYLON.GUI {
+    /**
         * Enum that determines the text-wrapping mode to use.
         */
     export enum TextWrapping {
@@ -2052,10 +2162,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;
@@ -2206,7 +2316,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;
     }
 }
@@ -2258,6 +2368,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;
@@ -2269,6 +2381,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 */
@@ -2281,7 +2396,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 {
@@ -2309,7 +2424,7 @@ declare module BABYLON.GUI {
                 */
             constructor(name?: string | undefined);
             protected _getTypeName(): string;
-            _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+            _draw(context: CanvasRenderingContext2D): void;
     }
 }
 declare module BABYLON.GUI {

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


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


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


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6147 - 5914
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.8",
+    "version": "4.0.0-alpha.10",
     "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.8"
+        "babylonjs": "4.0.0-alpha.10"
     },
     "engines": {
         "node": "*"

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 12 - 12
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.8",
+    "version": "4.0.0-alpha.10",
     "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.8",
-        "babylonjs-gui": "4.0.0-alpha.8",
-        "babylonjs-loaders": "4.0.0-alpha.8",
-        "babylonjs-serializers": "4.0.0-alpha.8"
+        "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"
     },
     "engines": {
         "node": "*"

+ 11 - 6
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -2206,13 +2206,18 @@ var BABYLON;
                 var sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(context + "/sampler", this.gltf.samplers, texture.sampler));
                 var samplerData = this._loadSampler("/samplers/" + sampler.index, sampler);
                 var image = ArrayItem.Get(context + "/source", this.gltf.images, texture.source);
-                var textureURL = null;
-                if (image.uri && !BABYLON.Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                    textureURL = this._uniqueRootUrl + image.uri;
+                var url = null;
+                if (image.uri) {
+                    if (BABYLON.Tools.IsBase64(image.uri)) {
+                        url = image.uri;
+                    }
+                    else if (this.babylonScene.getEngine().textureFormatInUse) {
+                        // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                        url = this._rootUrl + image.uri;
+                    }
                 }
                 var deferred = new BABYLON.Deferred();
-                var babylonTexture = new BABYLON.Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
                     if (!_this._disposed) {
                         deferred.resolve();
                     }
@@ -2222,7 +2227,7 @@ var BABYLON;
                     }
                 });
                 promises.push(deferred.promise);
-                if (!textureURL) {
+                if (!url) {
                     promises.push(this.loadImageAsync("/images/" + image.index, image).then(function (data) {
                         var name = image.uri || _this._fileName + "#image" + image.index;
                         var dataUrl = "data:" + _this._uniqueRootUrl + name;

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


+ 11 - 6
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -4414,13 +4414,18 @@ var BABYLON;
                 var sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(context + "/sampler", this.gltf.samplers, texture.sampler));
                 var samplerData = this._loadSampler("/samplers/" + sampler.index, sampler);
                 var image = ArrayItem.Get(context + "/source", this.gltf.images, texture.source);
-                var textureURL = null;
-                if (image.uri && !BABYLON.Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                    textureURL = this._uniqueRootUrl + image.uri;
+                var url = null;
+                if (image.uri) {
+                    if (BABYLON.Tools.IsBase64(image.uri)) {
+                        url = image.uri;
+                    }
+                    else if (this.babylonScene.getEngine().textureFormatInUse) {
+                        // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                        url = this._rootUrl + image.uri;
+                    }
                 }
                 var deferred = new BABYLON.Deferred();
-                var babylonTexture = new BABYLON.Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
                     if (!_this._disposed) {
                         deferred.resolve();
                     }
@@ -4430,7 +4435,7 @@ var BABYLON;
                     }
                 });
                 promises.push(deferred.promise);
-                if (!textureURL) {
+                if (!url) {
                     promises.push(this.loadImageAsync("/images/" + image.index, image).then(function (data) {
                         var name = image.uri || _this._fileName + "#image" + image.index;
                         var dataUrl = "data:" + _this._uniqueRootUrl + name;

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


+ 11 - 6
dist/preview release/loaders/babylonjs.loaders.js

@@ -5476,13 +5476,18 @@ var BABYLON;
                 var sampler = (texture.sampler == undefined ? GLTFLoader._DefaultSampler : ArrayItem.Get(context + "/sampler", this.gltf.samplers, texture.sampler));
                 var samplerData = this._loadSampler("/samplers/" + sampler.index, sampler);
                 var image = ArrayItem.Get(context + "/source", this.gltf.images, texture.source);
-                var textureURL = null;
-                if (image.uri && !BABYLON.Tools.IsBase64(image.uri) && this.babylonScene.getEngine().textureFormatInUse) {
-                    // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
-                    textureURL = this._uniqueRootUrl + image.uri;
+                var url = null;
+                if (image.uri) {
+                    if (BABYLON.Tools.IsBase64(image.uri)) {
+                        url = image.uri;
+                    }
+                    else if (this.babylonScene.getEngine().textureFormatInUse) {
+                        // If an image uri and a texture format is set like (eg. KTX) load from url instead of blob to support texture format and fallback
+                        url = this._rootUrl + image.uri;
+                    }
                 }
                 var deferred = new BABYLON.Deferred();
-                var babylonTexture = new BABYLON.Texture(textureURL, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(url, this.babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
                     if (!_this._disposed) {
                         deferred.resolve();
                     }
@@ -5492,7 +5497,7 @@ var BABYLON;
                     }
                 });
                 promises.push(deferred.promise);
-                if (!textureURL) {
+                if (!url) {
                     promises.push(this.loadImageAsync("/images/" + image.index, image).then(function (data) {
                         var name = image.uri || _this._fileName + "#image" + image.index;
                         var dataUrl = "data:" + _this._uniqueRootUrl + name;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 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.8",
+    "version": "4.0.0-alpha.10",
     "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.8",
-        "babylonjs": "4.0.0-alpha.8"
+        "babylonjs-gltf2interface": "4.0.0-alpha.10",
+        "babylonjs": "4.0.0-alpha.10"
     },
     "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.8",
+    "version": "4.0.0-alpha.10",
     "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.8"
+        "babylonjs": "4.0.0-alpha.10"
     },
     "engines": {
         "node": "*"

+ 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.8",
+    "version": "4.0.0-alpha.10",
     "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.8"
+        "babylonjs": "4.0.0-alpha.10"
     },
     "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.8",
+    "version": "4.0.0-alpha.10",
     "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.8"
+        "babylonjs": "4.0.0-alpha.10"
     },
     "engines": {
         "node": "*"

+ 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.8",
+    "version": "4.0.0-alpha.10",
     "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.8",
-        "babylonjs-gltf2interface": "4.0.0-alpha.8"
+        "babylonjs": "4.0.0-alpha.10",
+        "babylonjs-gltf2interface": "4.0.0-alpha.10"
     },
     "engines": {
         "node": "*"

+ 1 - 15
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): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 declare module BabylonViewer {
@@ -1558,20 +1558,6 @@ declare module BabylonViewer {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 declare module BabylonViewer {
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-declare module BabylonViewer {
 }
 declare module BabylonViewer {
     export interface IEnvironmentMapConfiguration {

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


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


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

@@ -985,14 +985,13 @@ declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
 }
 
 declare module 'babylonjs-viewer/optimizer/custom' {
-    import { extendedUpgrade } from "babylonjs-viewer/optimizer/custom/extended";
     import { SceneManager } from "babylonjs-viewer/managers/sceneManager";
     /**
       *
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 
@@ -1663,22 +1662,6 @@ declare module 'babylonjs-viewer/loader/plugins' {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 
-declare module 'babylonjs-viewer/optimizer/custom/extended' {
-    import { SceneManager } from 'babylonjs-viewer/managers/sceneManager';
-    /**
-        * A custom upgrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedUpgrade(sceneManager: SceneManager): boolean;
-    /**
-        * A custom degrade-oriented function configuration for the scene optimizer.
-        *
-        * @param viewer the viewer to optimize
-        */
-    export function extendedDegrade(sceneManager: SceneManager): boolean;
-}
-
 declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/cameraConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/colorGradingConfiguration';

+ 21 - 2
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))
@@ -18,6 +18,8 @@
 - GUI:
   - Added new [ImageBasedSlider](http://doc.babylonjs.com/how_to/gui#imagebasedslider) to let users customize sliders using images ([Deltakosh](https://github.com/deltakosh))
   - Added support for clipboard events to let users perform `cut`, `copy` and `paste` events ([Saket Saurabh](https://github.com/ssaket))
+  - Added new [ScrollViewer](https://doc.babylonjs.com/how_to/scrollviewer) with mouse wheel scrolling for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/) / [Deltakosh](https://github.com/deltakosh))
+  - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
 
 ## Updates
 
@@ -31,9 +33,12 @@
 - 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 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))
@@ -70,6 +75,8 @@
 - Added support for overriding the mesh used for the world matrix for a mesh with a skeleton ([bghgary](https://github.com/bghgary))
 - 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))
 
 ### glTF Loader
 
@@ -88,6 +95,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))
@@ -102,6 +111,12 @@
 - PointerDragBahavior using Mesh as base type, causing type-checking problems with AbstractMesh ([Poolminer](https://github.com/Poolminer/))
 - 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))
@@ -115,6 +130,9 @@
 - Added a `DeepImmutable<T>` type to specifiy that a referenced object should be considered recursively immutable, meaning that all its properties are `readonly` and that if a property is a reference to an object, this object is also recursively immutable. ([barroij](https://github.com/barroij))
 - Fixed `VideoTexture` poster property when autoplay is turned off.
 - 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))
 
 ### Viewer
 
@@ -150,3 +168,4 @@
   - _Note: The root node is still a `Mesh` object and is still the first in the returned list of meshes_
   - `TransformNode` objects are excluded from the returned list of meshes when importing mesh
   - `TransformNode` objects do not raise `onMeshLoaded` events
+- `xAxisMesh`, `yAxisMesh`, and `zAxisMesh` of `AxesViewer` was renamed to `xAxis`, `yAxis`, and `zAxis` respectively and now return a `TransformNode` to represent the parent node of the cylinder and line of the arrow ([bghgary](https://github.com/bghgary))

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

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

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

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

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

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

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1527 - 1479
gui/src/2D/controls/colorpicker.ts


+ 86 - 54
gui/src/2D/controls/container.ts

@@ -13,7 +13,7 @@ export class Container extends Control {
     /** @hidden */
     protected _measureForChildren = Measure.Empty();
     /** @hidden */
-    protected _background: string;
+    protected _background = "";
     /** @hidden */
     protected _adaptWidthToChildren = false;
     /** @hidden */
@@ -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);
 

+ 221 - 76
gui/src/2D/controls/control.ts

@@ -20,8 +20,6 @@ export class Control {
     private _alphaSet = false;
     private _zIndex = 0;
     /** @hidden */
-    public _root: Nullable<Container>;
-    /** @hidden */
     public _host: AdvancedDynamicTexture;
     /** Gets or sets the control parent */
     public parent: Nullable<Container>;
@@ -45,7 +43,8 @@ export class Control {
     protected _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
     /** @hidden */
     protected _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
-    private _isDirty = true;
+    /** @hidden */
+    protected _isDirty = true;
     /** @hidden */
     public _tempParentMeasure = Measure.Empty();
     /** @hidden */
@@ -68,7 +67,6 @@ export class Control {
     protected _invertTransformMatrix = Matrix2D.Identity();
     /** @hidden */
     protected _transformedPosition = Vector2.Zero();
-    private _onlyMeasureMode = false;
     private _isMatrixDirty = true;
     private _cachedOffsetX: number;
     private _cachedOffsetY: number;
@@ -85,6 +83,12 @@ export class Control {
     protected _isEnabled = true;
     protected _disabledColor = "#9a9a9a";
     /** @hidden */
+    protected _rebuildLayout = false;
+
+    /** @hidden */
+    public _isClipped = false;
+
+    /** @hidden */
     public _tag: any;
 
     /**
@@ -107,14 +111,65 @@ export class Control {
     /** Gets or sets a boolean indicating if the children are clipped to the current control bounds */
     public clipChildren = true;
 
+    private _shadowOffsetX = 0;
     /** Gets or sets a value indicating the offset to apply on X axis to render the shadow */
-    public shadowOffsetX = 0;
+    public get shadowOffsetX() {
+        return this._shadowOffsetX;
+    }
+
+    public set shadowOffsetX(value: number) {
+        if (this._shadowOffsetX === value) {
+            return;
+        }
+
+        this._shadowOffsetX = value;
+        this._markAsDirty();
+    }
+
+    private _shadowOffsetY = 0;
     /** Gets or sets a value indicating the offset to apply on Y axis to render the shadow */
-    public shadowOffsetY = 0;
+    public get shadowOffsetY() {
+        return this._shadowOffsetY;
+    }
+
+    public set shadowOffsetY(value: number) {
+        if (this._shadowOffsetY === value) {
+            return;
+        }
+
+        this._shadowOffsetY = value;
+        this._markAsDirty();
+    }
+
+    private _shadowBlur = 0;
     /** Gets or sets a value indicating the amount of blur to use to render the shadow */
-    public shadowBlur = 0;
+    public get shadowBlur() {
+        return this._shadowBlur;
+    }
+
+    public set shadowBlur(value: number) {
+        if (this._shadowBlur === value) {
+            return;
+        }
+
+        this._shadowBlur = value;
+        this._markAsDirty();
+    }
+
+    private _shadowColor = 'black';
     /** Gets or sets a value indicating the color of the shadow (black by default ie. "#000") */
-    public shadowColor = '#000';
+    public get shadowColor() {
+        return this._shadowColor;
+    }
+
+    public set shadowColor(value: string) {
+        if (this._shadowColor === value) {
+            return;
+        }
+
+        this._shadowColor = value;
+        this._markAsDirty();
+    }
 
     /** Gets or sets the cursor to use when the control is hovered */
     public hoverCursor = "";
@@ -184,6 +239,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;
@@ -524,8 +586,8 @@ export class Control {
 
         this._zIndex = value;
 
-        if (this._root) {
-            this._root._reOrderControl(this);
+        if (this.parent) {
+            this.parent._reOrderControl(this);
         }
     }
 
@@ -797,6 +859,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;
@@ -865,7 +944,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;
         }
@@ -910,7 +989,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");
             }
@@ -931,7 +1010,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);
     }
 
@@ -961,6 +1039,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();
@@ -995,8 +1085,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();
@@ -1030,7 +1119,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);
         }
@@ -1051,7 +1140,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);
     }
 
@@ -1082,9 +1171,36 @@ 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._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
@@ -1109,64 +1225,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 */
@@ -1276,7 +1358,70 @@ export class Control {
     }
 
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    protected _clipForChildren(context: CanvasRenderingContext2D): void {
+        // DO nothing
+    }
+
+    private _clip(context: CanvasRenderingContext2D) {
+        context.beginPath();
+
+        if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
+            var shadowOffsetX = this.shadowOffsetX;
+            var shadowOffsetY = this.shadowOffsetY;
+            var shadowBlur = this.shadowBlur;
+
+            var leftShadowOffset = Math.min(Math.min(shadowOffsetX, 0) - shadowBlur * 2, 0);
+            var rightShadowOffset = Math.max(Math.max(shadowOffsetX, 0) + shadowBlur * 2, 0);
+            var topShadowOffset = Math.min(Math.min(shadowOffsetY, 0) - shadowBlur * 2, 0);
+            var bottomShadowOffset = Math.max(Math.max(shadowOffsetY, 0) + shadowBlur * 2, 0);
+
+            context.rect(this._currentMeasure.left + leftShadowOffset,
+                this._currentMeasure.top + topShadowOffset,
+                this._currentMeasure.width + rightShadowOffset - leftShadowOffset,
+                this._currentMeasure.height + bottomShadowOffset - topShadowOffset);
+        } else {
+            context.rect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+        }
+
+        context.clip();
+    }
+
+    /** @hidden */
+    public _render(context: CanvasRenderingContext2D): boolean {
+        if (!this.isVisible || this.notRenderable || this._isClipped) {
+            this._isDirty = false;
+            return false;
+        }
+        context.save();
+
+        this._applyStates(context);
+
+        // Transform
+        this._transform(context);
+
+        // Clip
+        if (this.clipChildren) {
+            this._clip(context);
+        }
+
+        if (this.onBeforeDrawObservable.hasObservers()) {
+            this.onBeforeDrawObservable.notifyObservers(this);
+        }
+
+        this._draw(context);
+        this._renderHighlight(context);
+
+        if (this.onAfterDrawObservable.hasObservers()) {
+            this.onAfterDrawObservable.notifyObservers(this);
+        }
+
+        context.restore();
+
+        return true;
+    }
+
+    /** @hidden */
+    public _draw(context: CanvasRenderingContext2D): void {
         // Do nothing
     }
 
@@ -1499,9 +1644,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[]) => {

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

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

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

@@ -13,12 +13,13 @@ export * from "./multiLine";
 export * from "./radioButton";
 export * from "./stackPanel";
 export * from "./selector";
+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";

+ 297 - 135
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";
 
 /**
@@ -34,7 +33,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>>;
 
@@ -305,6 +306,7 @@ export class InputText extends Control implements IFocusableControl {
         super(name);
 
         this.text = text;
+        this.isPointerBlocker = true;
     }
 
     /** @hidden */
@@ -481,58 +483,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 +646,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();
     }
 
     /**
@@ -634,146 +770,158 @@ 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;
-            }
+        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;
-
-                    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) {
+        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();
 
-                // 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;
+        if (this._isFocused && this._textWidth > availableWidth) {
+            let textLeft = clipTextLeft - this._textWidth + availableWidth;
+            if (!this._scrollLeft) {
+                this._scrollLeft = textLeft;
+            }
+        } else {
+            this._scrollLeft = clipTextLeft;
+        }
 
-                    } while (currentSize < absoluteCursorPosition && (text.length >= this._cursorOffset));
+        context.fillText(text, this._scrollLeft, this._currentMeasure.top + rootY);
 
-                    // Find closest move
-                    if (Math.abs(absoluteCursorPosition - currentSize) > previousDist) {
-                        this._cursorOffset--;
+        // Cursor
+        if (this._isFocused) {
+
+            // Need to move cursor
+            if (this._clickedCoordinate) {
+                var rightPosition = this._scrollLeft + this._textWidth;
+                var absoluteCursorPosition = rightPosition - this._clickedCoordinate;
+                var currentSize = 0;
+                this._cursorOffset = 0;
+                var previousDist = 0;
+                do {
+                    if (this._cursorOffset) {
+                        previousDist = Math.abs(absoluteCursorPosition - currentSize);
                     }
+                    this._cursorOffset++;
+                    currentSize = context.measureText(text.substr(text.length - this._cursorOffset, this._cursorOffset)).width;
 
-                    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;
+            }
 
-                    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();
-                    }
+            // 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 (!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();
@@ -800,6 +948,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 +964,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);
     }
 

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

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

@@ -0,0 +1,359 @@
+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;
+
+    /**
+     * 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;
+
+        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';
         }
     }
 

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

+ 12 - 0
inspector/src/components/actionTabs/actionTabs.scss

@@ -5,6 +5,10 @@
     bottom: 0px;
 }
 
+#__resizable_base__ {
+    display: none;
+}
+
 #actionTabs {
     background: #333333;
     height: 100%;
@@ -140,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;
@@ -729,6 +737,10 @@
                         display: flex;
                         align-items: center;   
                         margin-right: 5px;
+
+                        select {
+                            width: 115px;
+                        }
                     }                    
                 }                   
 

+ 3 - 2
inspector/src/components/actionTabs/actionTabsComponent.tsx

@@ -17,6 +17,7 @@ interface IActionTabsComponentProps {
     noCommands?: boolean,
     noHeader?: boolean,
     noExpand?: boolean,
+    noClose?: boolean,
     popupMode?: boolean,
     onPopup?: () => void,
     onClose?: () => void,
@@ -101,7 +102,7 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
                 <div id="actionTabs">
                     {
                         !this.props.noHeader &&
-                        <HeaderComponent title="INSPECTOR" handleBack={true} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                        <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                     }
                     {this.renderContent()}
                 </div>
@@ -124,7 +125,7 @@ export class ActionTabsComponent extends React.Component<IActionTabsComponentPro
             <Resizable id="actionTabs" minWidth={300} maxWidth={600} size={{ height: "100%" }} minHeight="100%" enable={{ top: false, right: false, bottom: false, left: true, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }}>
                 {
                     !this.props.noHeader &&
-                    <HeaderComponent title="INSPECTOR" handleBack={true} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                    <HeaderComponent title="INSPECTOR" handleBack={true} noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 }
                 {this.renderContent()}
             </Resizable>

+ 31 - 6
inspector/src/components/actionTabs/lineContainerComponent.tsx

@@ -9,14 +9,26 @@ interface ILineContainerComponentProps {
 }
 
 export class LineContainerComponent extends React.Component<ILineContainerComponentProps, { isExpanded: boolean }> {
+    private static _InMemoryStorage: {[key: string]: boolean};
+    
     constructor(props: ILineContainerComponentProps) {
         super(props);
 
         let initialState: boolean;
 
-        if (typeof (Storage) !== "undefined" && localStorage.getItem(this.props.title) !== null) {
-            initialState = localStorage.getItem(this.props.title) === "true";
-        } else {
+        try
+        { 
+            if (LineContainerComponent._InMemoryStorage && LineContainerComponent._InMemoryStorage[this.props.title] !== undefined) {
+                initialState = LineContainerComponent._InMemoryStorage[this.props.title];
+            } else if (typeof (Storage) !== "undefined" && localStorage.getItem(this.props.title) !== null) {
+                initialState = localStorage.getItem(this.props.title) === "true";
+            } else {
+                initialState = !this.props.closed;
+            }   
+        }
+        catch (e) {
+            LineContainerComponent._InMemoryStorage = {};
+            LineContainerComponent._InMemoryStorage[this.props.title] = !this.props.closed
             initialState = !this.props.closed;
         }
 
@@ -24,10 +36,23 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
     }
 
     switchExpandedState(): void {
-        if (typeof (Storage) !== "undefined") {
-            localStorage.setItem(this.props.title, !this.state.isExpanded ? "true" : "false");
+        const newState = !this.state.isExpanded;
+        
+        try
+        { 
+            if (LineContainerComponent._InMemoryStorage) {
+                LineContainerComponent._InMemoryStorage[this.props.title] = newState;
+            } else if (typeof (Storage) !== "undefined") {
+                localStorage.setItem(this.props.title, newState ? "true" : "false");
+            }
+        }
+        catch (e) {
+            LineContainerComponent._InMemoryStorage = {};
+            LineContainerComponent._InMemoryStorage[this.props.title] = newState;
         }
-        this.setState({ isExpanded: !this.state.isExpanded });
+        
+        this.setState({ isExpanded: newState });
+
     }
 
     renderHeader() {

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

@@ -33,7 +33,7 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
         }
 
         const newValue = nextProps.target[nextProps.propertyName];
-        if (newValue !== nextState.value) {
+        if (newValue != null && newValue !== nextState.value) {
             nextState.value = newValue;
             return true;
         }

+ 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()} />

+ 36 - 2
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,14 +67,17 @@ 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} />
                     }
                     {
-                        (control as any).background &&
+                        (control as any).background !== undefined &&
                         <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} />
@@ -77,6 +105,12 @@ export class CommonControlPropertyGridComponent extends React.Component<ICommonC
                     <TextInputLineComponent lockObject={this.props.lockObject} label="Weight" target={control} propertyName="fontWeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <TextInputLineComponent lockObject={this.props.lockObject} label="Style" target={control} propertyName="fontStyle" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
+                <LineContainerComponent title="SHADOWS" closed={true}>
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Color" target={control} propertyName="shadowColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Offset X" target={control} propertyName="shadowOffsetX" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Offset Y" target={control} propertyName="shadowOffsetY" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Blur" target={control} propertyName="shadowBlur" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
             </div>
         );
     }

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

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

+ 15 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/gui/textBlockPropertyGridComponent.tsx

@@ -7,6 +7,8 @@ import { LineContainerComponent } from "../../../lineContainerComponent";
 import { TextInputLineComponent } from "../../../lines/textInputLineComponent";
 import { LockObject } from "../lockObject";
 import { OptionsLineComponent } from "../../../lines/optionsLineComponent";
+import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
+import { FloatLineComponent } from "../../../lines/floatLineComponent";
 
 interface ITextBlockPropertyGridComponentProps {
     textBlock: TextBlock,
@@ -34,6 +36,12 @@ export class TextBlockPropertyGridComponent extends React.Component<ITextBlockPr
             { label: "Center", value: BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER },
         ];
 
+        var wrappingOptions = [
+            { label: "Clip", value: BABYLON.GUI.TextWrapping.Clip },
+            { label: "Ellipsis", value: BABYLON.GUI.TextWrapping.Ellipsis },
+            { label: "Word wrap", value: BABYLON.GUI.TextWrapping.WordWrap },
+        ];
+
         return (
             <div className="pane">
                 <CommonControlPropertyGridComponent lockObject={this.props.lockObject} control={textBlock} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
@@ -41,6 +49,13 @@ export class TextBlockPropertyGridComponent extends React.Component<ITextBlockPr
                     <TextInputLineComponent lockObject={this.props.lockObject} label="Text" target={textBlock} propertyName="text" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <OptionsLineComponent label="Horizontal text alignment" options={horizontalOptions} target={textBlock} propertyName="textHorizontalAlignment" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <OptionsLineComponent label="Vertical text alignment" options={verticalOptions} target={textBlock} propertyName="textVerticalAlignment" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <CheckBoxLineComponent label="Resize to fit" target={textBlock} propertyName="resizeToFit" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <OptionsLineComponent label="Wrapping" options={wrappingOptions} target={textBlock} propertyName="textWrapping" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Line spacing" target={textBlock} propertyName="lineSpacing" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                </LineContainerComponent>
+                <LineContainerComponent title="OUTLINE">
+                    <FloatLineComponent lockObject={this.props.lockObject} label="Outline width" target={textBlock} propertyName="outlineWidth" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Outline color" target={textBlock} propertyName="outlineColor" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
             </div>
         );

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

@@ -7,7 +7,6 @@ import { TextLineComponent } from "../../../lines/textLineComponent";
 import { CheckBoxLineComponent } from "../../../lines/checkBoxLineComponent";
 import { TextureLineComponent } from "../../../lines/textureLineComponent";
 import { FloatLineComponent } from "../../../lines/floatLineComponent";
-import { AdvancedDynamicTexture } from "babylonjs-gui";
 import { OptionsLineComponent } from "../../../lines/optionsLineComponent";
 import { FileButtonLineComponent } from "../../../lines/fileButtonLineComponent";
 import { LockObject } from "../lockObject";
@@ -47,7 +46,6 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
 
     render() {
         const texture = this.props.texture;
-        const adtTexture = texture instanceof AdvancedDynamicTexture ? texture as AdvancedDynamicTexture : null;
 
         var samplingMode = [
             { label: "Nearest", value: BABYLON.Texture.NEAREST_NEAREST },
@@ -76,14 +74,14 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
                     }
                 </LineContainerComponent>
                 {
-                    adtTexture &&
+                    (texture as any).rootContainer &&
                     <LineContainerComponent title="ADVANCED TEXTURE PROPERTIES">
-                        <SliderLineComponent label="Render scale" minimum={0.1} maximum={5} step={0.1} target={adtTexture} propertyName="renderScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <CheckBoxLineComponent label="Premultiply alpha" target={adtTexture} propertyName="premulAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal width" target={adtTexture} propertyName="idealWidth" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal height" target={adtTexture} propertyName="idealHeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <CheckBoxLineComponent label="Use smallest ideal" target={adtTexture} propertyName="useSmallestIdeal" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                        <CheckBoxLineComponent label="Render at ideal size" target={adtTexture} propertyName="renderAtIdealSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <SliderLineComponent label="Render scale" minimum={0.1} maximum={5} step={0.1} target={texture} propertyName="renderScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <CheckBoxLineComponent label="Premultiply alpha" target={texture} propertyName="premulAlpha" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal width" target={texture} propertyName="idealWidth" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <FloatLineComponent lockObject={this.props.lockObject} label="Ideal height" target={texture} propertyName="idealHeight" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <CheckBoxLineComponent label="Use smallest ideal" target={texture} propertyName="useSmallestIdeal" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                        <CheckBoxLineComponent label="Render at ideal size" target={texture} propertyName="renderAtIdealSize" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     </LineContainerComponent>
                 }
                 <LineContainerComponent title="TRANSFORM">
@@ -92,7 +90,7 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
                         <div>
                             <FloatLineComponent lockObject={this.props.lockObject} label="U offset" target={texture} propertyName="uOffset" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="V offset" target={texture} propertyName="vOffset" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
-                            <FloatLineComponent lockObject={this.props.lockObject} label="V scale" target={texture} propertyName="uScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                            <FloatLineComponent lockObject={this.props.lockObject} label="U scale" target={texture} propertyName="uScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="V scale" target={texture} propertyName="vScale" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="U angle" target={texture} propertyName="uAng" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                             <FloatLineComponent lockObject={this.props.lockObject} label="V angle" target={texture} propertyName="vAng" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

+ 3 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/meshes/axesViewerComponent.tsx

@@ -40,9 +40,9 @@ export class AxesViewerComponent extends React.Component<IAxisViewerComponentPro
         const y = new BABYLON.Vector3(0, 1, 0);
         const z = new BABYLON.Vector3(0, 0, 1);
 
-        viewer.xAxisMesh!.reservedDataStore = { hidden: true };
-        viewer.yAxisMesh!.reservedDataStore = { hidden: true };
-        viewer.zAxisMesh!.reservedDataStore = { hidden: true };
+        viewer.xAxis.reservedDataStore = { hidden: true };
+        viewer.yAxis.reservedDataStore = { hidden: true };
+        viewer.zAxis.reservedDataStore = { hidden: true };
 
         node.reservedDataStore.onBeforeRenderObserver = scene.onBeforeRenderObservable.add(() => {
             let matrix = node.getWorldMatrix();

+ 3 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/gridPropertyGridComponent.tsx

@@ -2,14 +2,14 @@ import * as React from "react";
 import { Scene, AbstractMesh, Nullable } from "babylonjs";
 import { CheckBoxLineComponent } from "../../lines/checkBoxLineComponent";
 
-interface IGridPropertyGridComponentProps {
+interface IRenderGridPropertyGridComponentProps {
     scene: Scene
 }
 
-export class GridPropertyGridComponent extends React.Component<IGridPropertyGridComponentProps, { isEnabled: boolean }> {
+export class RenderGridPropertyGridComponent extends React.Component<IRenderGridPropertyGridComponentProps, { isEnabled: boolean }> {
     private _gridMesh: Nullable<AbstractMesh>;
 
-    constructor(props: IGridPropertyGridComponentProps) {
+    constructor(props: IRenderGridPropertyGridComponentProps) {
         super(props);
         this.state = { isEnabled: false };
     }

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

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

+ 4 - 0
inspector/src/components/embedHost/embedHost.scss

@@ -5,6 +5,10 @@
     bottom: 0px;
 }
 
+#__resizable_base__ {
+    display: none;
+}
+
 #embed {
     background: #333333;
     height: 100%;

+ 4 - 2
inspector/src/components/embedHost/embedHostComponent.tsx

@@ -14,6 +14,8 @@ interface IEmbedHostComponentProps {
     scene: Scene,
     globalState: GlobalState,
     popupMode: boolean,
+    noClose?: boolean,
+    noExpand?: boolean,
     onClose: () => void,
     onPopup: () => void
 }
@@ -80,7 +82,7 @@ export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps
         if (this.props.popupMode) {
             return (
                 <div id="embed">
-                    <HeaderComponent title="INSPECTOR" handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                    <HeaderComponent title="INSPECTOR" noClose={this.props.noClose} noExpand={this.props.noExpand} handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                     {this.renderContent()}
                 </div>
             );
@@ -100,7 +102,7 @@ export class EmbedHostComponent extends React.Component<IEmbedHostComponentProps
 
         return (
             <Resizable id="embed" minWidth={300} maxWidth={600} size={{ height: "100%" }} minHeight="100%" enable={{ top: false, right: false, bottom: false, left: true, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }}>
-                <HeaderComponent title="INSPECTOR" handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                <HeaderComponent title="INSPECTOR" noClose={this.props.noClose} noExpand={this.props.noExpand} handleBack={true} onClose={() => this.props.onClose()} onPopup={() => this.props.onPopup()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 {this.renderContent()}
             </Resizable>
         );

+ 2 - 1
inspector/src/components/headerComponent.tsx

@@ -7,6 +7,7 @@ export interface IHeaderComponentProps {
     title: string,
     handleBack?: boolean,
     noExpand?: boolean,
+    noClose?: boolean,
     noCommands?: boolean,
     onPopup: () => void,
     onClose: () => void,
@@ -89,7 +90,7 @@ export class HeaderComponent extends React.Component<IHeaderComponentProps, { is
                         </div>
                     }
                     {
-                        !this.props.noCommands &&
+                        !this.props.noCommands && !this.props.noClose &&
                         <div className="close" onClick={() => this.props.onClose()}>
                             <FontAwesomeIcon icon={faTimes} />
                         </div>

+ 2 - 2
inspector/src/components/sceneExplorer/entities/cameraTreeItemComponent.tsx

@@ -38,10 +38,10 @@ export class CameraTreeItemComponent extends React.Component<ICameraTreeItemComp
         const camera = this.props.camera;
         const scene = camera.getScene();
         this._onActiveCameraObserver = scene.onActiveCameraChanged.add(() => {
-            if (this.state.isActive) {
+            // This will deactivate the previous camera when the camera is changed. Multiple camera's cycle frequently so only do this for single cameras
+            if (this.state.isActive && scene.activeCameras.length <= 1) {
                 camera.detachControl(scene.getEngine().getRenderingCanvas()!);
             }
-
             this.setState({ isActive: scene.activeCamera === camera });
         });
     }

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

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

+ 4 - 0
inspector/src/components/sceneExplorer/sceneExplorer.scss

@@ -9,6 +9,10 @@
     }
 }
 
+#__resizable_base__ {
+    display: none;
+}
+
 #sceneExplorer {
     background: #333333;
     height: 100%;

+ 3 - 2
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -33,6 +33,7 @@ interface ISceneExplorerComponentProps {
     noCommands?: boolean,
     noHeader?: boolean,
     noExpand?: boolean,
+    noClose?: boolean,
     extensibilityGroups?: IExplorerExtensibilityGroup[],
     globalState: GlobalState,
     popupMode?: boolean,
@@ -234,7 +235,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                 <div id="sceneExplorer">
                     {
                         !this.props.noHeader &&
-                        <HeaderComponent title="SCENE EXPLORER" noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
+                        <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
                     }
                     {this.renderContent()}
                 </div>
@@ -273,7 +274,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
             <Resizable tabIndex={-1} id="sceneExplorer" ref="sceneExplorer" size={{ height: "100%" }} minWidth={300} maxWidth={600} minHeight="100%" enable={{ top: false, right: true, bottom: false, left: false, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false }} onKeyDown={keyEvent => this.processKeys(keyEvent)}>
                 {
                     !this.props.noHeader &&
-                    <HeaderComponent title="SCENE EXPLORER" noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
+                    <HeaderComponent title="SCENE EXPLORER" noClose={this.props.noClose} noExpand={this.props.noExpand} noCommands={this.props.noCommands} onClose={() => this.onClose()} onPopup={() => this.onPopup()} />
                 }
                 {this.renderContent()}
             </Resizable>

+ 0 - 0
inspector/src/inspector.ts


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