瀏覽代碼

Merge pull request #5587 from BabylonJS/gui-measuremode

Gui measuremode
David Catuhe 6 年之前
父節點
當前提交
b39e8ea1f9
共有 31 個文件被更改,包括 1576 次插入1189 次删除
  1. 1 0
      dist/preview release/what's new.md
  2. 7 11
      gui/src/2D/advancedDynamicTexture.ts
  3. 6 1
      gui/src/2D/controls/button.ts
  4. 32 34
      gui/src/2D/controls/checkbox.ts
  5. 35 38
      gui/src/2D/controls/colorpicker.ts
  6. 68 47
      gui/src/2D/controls/container.ts
  7. 135 72
      gui/src/2D/controls/control.ts
  8. 2 3
      gui/src/2D/controls/displayGrid.ts
  9. 11 5
      gui/src/2D/controls/grid.ts
  10. 51 35
      gui/src/2D/controls/image.ts
  11. 4 4
      gui/src/2D/controls/index.ts
  12. 110 114
      gui/src/2D/controls/inputText.ts
  13. 9 11
      gui/src/2D/controls/line.ts
  14. 19 21
      gui/src/2D/controls/multiLine.ts
  15. 31 35
      gui/src/2D/controls/radioButton.ts
  16. 0 435
      gui/src/2D/controls/scrollViewer.ts
  17. 375 0
      gui/src/2D/controls/scrollViewers/scrollViewer.ts
  18. 74 0
      gui/src/2D/controls/scrollViewers/scrollViewerWindow.ts
  19. 1 1
      gui/src/2D/controls/selector.ts
  20. 0 227
      gui/src/2D/controls/slider.ts
  21. 4 3
      gui/src/2D/controls/baseSlider.ts
  22. 46 44
      gui/src/2D/controls/imageBasedSlider.ts
  23. 155 0
      gui/src/2D/controls/sliders/scrollBar.ts
  24. 240 0
      gui/src/2D/controls/sliders/slider.ts
  25. 41 21
      gui/src/2D/controls/stackPanel.ts
  26. 49 25
      gui/src/2D/controls/textBlock.ts
  27. 13 0
      gui/src/2D/valueAndUnit.ts
  28. 9 1
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  29. 1 1
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/commonControlPropertyGridComponent.tsx
  30. 42 0
      inspector/src/components/actionTabs/tabs/propertyGrids/gui/scrollViewerPropertyGridComponent.tsx
  31. 5 0
      inspector/src/components/sceneExplorer/entities/gui/advancedDynamicTextureTreeItemComponent.tsx

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

@@ -19,6 +19,7 @@
   - Added new [ImageBasedSlider](http://doc.babylonjs.com/how_to/gui#imagebasedslider) to let users customize sliders using images ([Deltakosh](https://github.com/deltakosh))
   - Added 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 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/))
   - Added new [ScrollViewer](https://doc.babylonjs.com/how_to/scrollviewer) with mouse wheel scrolling for larger containers to be viewed using Sliders ([JohnK](https://github.com/BabylonJSGuide/))
+  - Moved to a measure / draw mechanism ([Deltakosh](https://github.com/deltakosh))
 
 
 ## Updates
 ## Updates
 
 

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

@@ -69,9 +69,6 @@ export class AdvancedDynamicTexture extends DynamicTexture {
     private _renderScale = 1;
     private _renderScale = 1;
     private _rootCanvas: Nullable<HTMLCanvasElement>;
     private _rootCanvas: Nullable<HTMLCanvasElement>;
 
 
-    /** @hidden */
-    public _needRedraw = false;
-
     /**
     /**
      * Define type to string to ensure compatibility across browsers
      * Define type to string to ensure compatibility across browsers
      * Safari doesn't support DataTransfer constructor
      * Safari doesn't support DataTransfer constructor
@@ -317,7 +314,7 @@ export class AdvancedDynamicTexture extends DynamicTexture {
             info.skipOnPointerObservable = true;
             info.skipOnPointerObservable = true;
         });
         });
 
 
-        this._rootContainer._link(null, this);
+        this._rootContainer._link(this);
 
 
         this.hasAlpha = true;
         this.hasAlpha = true;
 
 
@@ -576,12 +573,10 @@ export class AdvancedDynamicTexture extends DynamicTexture {
         context.font = "18px Arial";
         context.font = "18px Arial";
         context.strokeStyle = "white";
         context.strokeStyle = "white";
         var measure = new Measure(0, 0, renderWidth, renderHeight);
         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 */
     /** @hidden */
@@ -833,16 +828,17 @@ export class AdvancedDynamicTexture extends DynamicTexture {
     }
     }
 
 
     private _attachToOnPointerOut(scene: Scene): void {
     private _attachToOnPointerOut(scene: Scene): void {
+
         this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add((pointerEvent) => {
         this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add((pointerEvent) => {
             if (this._lastControlOver[pointerEvent.pointerId]) {
             if (this._lastControlOver[pointerEvent.pointerId]) {
                 this._lastControlOver[pointerEvent.pointerId]._onPointerOut(this._lastControlOver[pointerEvent.pointerId]);
                 this._lastControlOver[pointerEvent.pointerId]._onPointerOut(this._lastControlOver[pointerEvent.pointerId]);
             }
             }
             delete 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();
                 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.thickness = 1;
         this.isPointerBlocker = true;
         this.isPointerBlocker = true;
 
 
+        let alphaStore: Nullable<number> = null;
+
         this.pointerEnterAnimation = () => {
         this.pointerEnterAnimation = () => {
+            alphaStore = this.alpha;
             this.alpha -= 0.1;
             this.alpha -= 0.1;
         };
         };
 
 
         this.pointerOutAnimation = () => {
         this.pointerOutAnimation = () => {
-            this.alpha += 0.1;
+            if (alphaStore !== null) {
+                this.alpha = alphaStore;
+            }
         };
         };
 
 
         this.pointerDownAnimation = () => {
         this.pointerDownAnimation = () => {

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

@@ -1,5 +1,4 @@
 import { Control } from "./control";
 import { Control } from "./control";
-import { Measure } from "../measure";
 import { Observable, Vector2 } from "babylonjs";
 import { Observable, Vector2 } from "babylonjs";
 import { StackPanel } from "./stackPanel";
 import { StackPanel } from "./stackPanel";
 import { TextBlock } from "./textBlock";
 import { TextBlock } from "./textBlock";
@@ -92,43 +91,42 @@ export class Checkbox extends Control {
     }
     }
 
 
     /** @hidden */
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
         context.save();
 
 
         this._applyStates(context);
         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();
         context.restore();
     }
     }
 
 

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

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

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

@@ -18,6 +18,8 @@ export class Container extends Control {
     protected _adaptWidthToChildren = false;
     protected _adaptWidthToChildren = false;
     /** @hidden */
     /** @hidden */
     protected _adaptHeightToChildren = false;
     protected _adaptHeightToChildren = false;
+    /** @hidden */
+    protected _rebuildLayout = false;
 
 
     /** Gets or sets a boolean indicating if the container should try to adapt to its children height */
     /** Gets or sets a boolean indicating if the container should try to adapt to its children height */
     public get adaptHeightToChildren(): boolean {
     public get adaptHeightToChildren(): boolean {
@@ -149,7 +151,7 @@ export class Container extends Control {
         if (index !== -1) {
         if (index !== -1) {
             return this;
             return this;
         }
         }
-        control._link(this, this._host);
+        control._link(this._host);
 
 
         control._markAllAsDirty();
         control._markAllAsDirty();
 
 
@@ -246,76 +248,100 @@ export class Container extends Control {
     }
     }
 
 
     /** @hidden */
     /** @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) {
         for (var child of this._children) {
-            child._link(this, host);
+            child._link(host);
         }
         }
     }
     }
 
 
     /** @hidden */
     /** @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) {
         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 computedWidth = -1;
             let computedHeight = -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._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();
         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 +393,6 @@ export class Container extends Control {
     }
     }
 
 
     /** @hidden */
     /** @hidden */
-    protected _clipForChildren(context: CanvasRenderingContext2D): void {
-        // DO nothing
-    }
-
-    /** @hidden */
     protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
     protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
         super._additionalProcessing(parentMeasure, context);
         super._additionalProcessing(parentMeasure, context);
 
 

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

@@ -20,8 +20,6 @@ export class Control {
     private _alphaSet = false;
     private _alphaSet = false;
     private _zIndex = 0;
     private _zIndex = 0;
     /** @hidden */
     /** @hidden */
-    public _root: Nullable<Container>;
-    /** @hidden */
     public _host: AdvancedDynamicTexture;
     public _host: AdvancedDynamicTexture;
     /** Gets or sets the control parent */
     /** Gets or sets the control parent */
     public parent: Nullable<Container>;
     public parent: Nullable<Container>;
@@ -45,7 +43,8 @@ export class Control {
     protected _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
     protected _horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
     /** @hidden */
     /** @hidden */
     protected _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
     protected _verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
-    private _isDirty = true;
+    /** @hidden */
+    protected _isDirty = true;
     /** @hidden */
     /** @hidden */
     public _tempParentMeasure = Measure.Empty();
     public _tempParentMeasure = Measure.Empty();
     /** @hidden */
     /** @hidden */
@@ -68,7 +67,6 @@ export class Control {
     protected _invertTransformMatrix = Matrix2D.Identity();
     protected _invertTransformMatrix = Matrix2D.Identity();
     /** @hidden */
     /** @hidden */
     protected _transformedPosition = Vector2.Zero();
     protected _transformedPosition = Vector2.Zero();
-    private _onlyMeasureMode = false;
     private _isMatrixDirty = true;
     private _isMatrixDirty = true;
     private _cachedOffsetX: number;
     private _cachedOffsetX: number;
     private _cachedOffsetY: number;
     private _cachedOffsetY: number;
@@ -84,6 +82,10 @@ export class Control {
     private _downPointerIds: { [id: number]: boolean } = {};
     private _downPointerIds: { [id: number]: boolean } = {};
     protected _isEnabled = true;
     protected _isEnabled = true;
     protected _disabledColor = "#9a9a9a";
     protected _disabledColor = "#9a9a9a";
+
+    /** @hidden */
+    public _isClipped = false;
+
     /** @hidden */
     /** @hidden */
     public _tag: any;
     public _tag: any;
 
 
@@ -575,8 +577,8 @@ export class Control {
 
 
         this._zIndex = value;
         this._zIndex = value;
 
 
-        if (this._root) {
-            this._root._reOrderControl(this);
+        if (this.parent) {
+            this.parent._reOrderControl(this);
         }
         }
     }
     }
 
 
@@ -848,6 +850,23 @@ export class Control {
         return "Control";
         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 */
     /** @hidden */
     public _resetFontCache(): void {
     public _resetFontCache(): void {
         this._fontSet = true;
         this._fontSet = true;
@@ -916,7 +935,7 @@ export class Control {
      * @param scene defines the hosting scene
      * @param scene defines the hosting scene
      */
      */
     public moveToVector3(position: Vector3, scene: Scene): void {
     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");
             Tools.Error("Cannot move a control to a vector3 if the control is not at root level");
             return;
             return;
         }
         }
@@ -961,7 +980,7 @@ export class Control {
      * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
      * @see http://doc.babylonjs.com/how_to/gui#tracking-positions
      */
      */
     public linkWithMesh(mesh: Nullable<AbstractMesh>): void {
     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) {
             if (mesh) {
                 Tools.Error("Cannot link a control to a mesh if the control is not at root level");
                 Tools.Error("Cannot link a control to a mesh if the control is not at root level");
             }
             }
@@ -982,7 +1001,6 @@ export class Control {
         this.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
         this.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
         this.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
         this.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
         this._linkedMesh = mesh;
         this._linkedMesh = mesh;
-        this._onlyMeasureMode = this._currentMeasure.width === 0 || this._currentMeasure.height === 0;
         this._host._linkedControls.push(this);
         this._host._linkedControls.push(this);
     }
     }
 
 
@@ -1046,8 +1064,7 @@ export class Control {
     }
     }
 
 
     /** @hidden */
     /** @hidden */
-    public _link(root: Nullable<Container>, host: AdvancedDynamicTexture): void {
-        this._root = root;
+    public _link(host: AdvancedDynamicTexture): void {
         this._host = host;
         this._host = host;
         if (this._host) {
         if (this._host) {
             this.uniqueId = this._host.getScene()!.getUniqueId();
             this.uniqueId = this._host.getScene()!.getUniqueId();
@@ -1081,7 +1098,7 @@ export class Control {
             this._isMatrixDirty = false;
             this._isMatrixDirty = false;
             this._flagDescendantsAsMatrixDirty();
             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);
             this._transformMatrix.invertToRef(this._invertTransformMatrix);
         }
         }
@@ -1102,7 +1119,7 @@ export class Control {
     }
     }
 
 
     /** @hidden */
     /** @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);
         context.strokeRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
     }
     }
 
 
@@ -1133,9 +1150,26 @@ export class Control {
     }
     }
 
 
     /** @hidden */
     /** @hidden */
-    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
+    public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
+        if (!this.isVisible || this.notRenderable) {
+            return false;
+        }
+        context.save();
+
+        this._applyStates(context);
+
+        this._processMeasures(parentMeasure, context);
+
+        context.restore();
+
+        this._isDirty = false;
+
+        return true;
+    }
+
+    /** @hidden */
+    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
         if (this._isDirty || !this._cachedParentMeasure.isEqualsTo(parentMeasure)) {
         if (this._isDirty || !this._cachedParentMeasure.isEqualsTo(parentMeasure)) {
-            this._isDirty = false;
             this._currentMeasure.copyFrom(parentMeasure);
             this._currentMeasure.copyFrom(parentMeasure);
 
 
             // Let children take some pre-measurement actions
             // Let children take some pre-measurement actions
@@ -1160,64 +1194,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 */
     /** @hidden */
@@ -1327,7 +1327,70 @@ export class Control {
     }
     }
 
 
     /** @hidden */
     /** @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
         // Do nothing
     }
     }
 
 
@@ -1550,9 +1613,9 @@ export class Control {
             this._styleObserver = null;
             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) {
         if (this._host) {

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

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

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

@@ -72,6 +72,11 @@ export class Grid extends Container {
             return this;
             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._rowDefinitions[index] = new ValueAndUnit(height, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE);
 
 
         this._markAsDirty();
         this._markAsDirty();
@@ -91,6 +96,11 @@ export class Grid extends Container {
             return this;
             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._columnDefinitions[index] = new ValueAndUnit(width, isPixel ? ValueAndUnit.UNITMODE_PIXEL : ValueAndUnit.UNITMODE_PERCENTAGE);
 
 
         this._markAsDirty();
         this._markAsDirty();
@@ -388,11 +398,7 @@ export class Grid extends Container {
         }
         }
     }
     }
 
 
-    protected _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
-        if (!this.isHighlighted) {
-            return;
-        }
-
+    public _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
         super._renderHighlightSpecific(context);
         super._renderHighlightSpecific(context);
 
 
         this._getGridDefinitions((lefts: number[], tops: number[], widths: number[], heights: number[]) => {
         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 { Control } from "./control";
 import { Nullable, Tools, Observable } from "babylonjs";
 import { Nullable, Tools, Observable } from "babylonjs";
-import { Measure } from "../measure";
+import { Measure } from "2D";
 
 
 /**
 /**
  * Class used to create 2D images
  * Class used to create 2D images
@@ -267,7 +267,31 @@ export class Image extends Control {
         this.height = this._domImage.height + "px";
         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();
         context.save();
 
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
@@ -297,41 +321,33 @@ export class Image extends Control {
         }
         }
 
 
         this._applyStates(context);
         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();
         context.restore();
     }
     }
 
 

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

@@ -13,13 +13,13 @@ export * from "./multiLine";
 export * from "./radioButton";
 export * from "./radioButton";
 export * from "./stackPanel";
 export * from "./stackPanel";
 export * from "./selector";
 export * from "./selector";
-export * from "./scrollViewer";
+export * from "./scrollViewers/scrollViewer";
 export * from "./textBlock";
 export * from "./textBlock";
 export * from "./virtualKeyboard";
 export * from "./virtualKeyboard";
 export * from "./rectangle";
 export * from "./rectangle";
 export * from "./displayGrid";
 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";
 export * from "./statics";

+ 110 - 114
gui/src/2D/controls/inputText.ts

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

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

@@ -159,7 +159,7 @@ export class Line extends Control {
         return "Line";
         return "Line";
     }
     }
 
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
         context.save();
 
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
@@ -170,18 +170,16 @@ export class Line extends Control {
         }
         }
 
 
         this._applyStates(context);
         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();
         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.
      * @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 {
     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");
             Tools.Error("Cannot move a control to a vector3 if the control is not at root level");
             return;
             return;
         }
         }

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

@@ -170,7 +170,7 @@ export class MultiLine extends Control {
         return "MultiLine";
         return "MultiLine";
     }
     }
 
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
         context.save();
 
 
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
         if (this.shadowBlur || this.shadowOffsetX || this.shadowOffsetY) {
@@ -182,32 +182,30 @@ export class MultiLine extends Control {
 
 
         this._applyStates(context);
         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();
         context.restore();
     }
     }

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

@@ -1,6 +1,5 @@
 import { Control } from "./control";
 import { Control } from "./control";
 import { Observable, Vector2 } from "babylonjs";
 import { Observable, Vector2 } from "babylonjs";
-import { Measure } from "../measure";
 import { StackPanel, TextBlock } from ".";
 import { StackPanel, TextBlock } from ".";
 
 
 /**
 /**
@@ -109,51 +108,48 @@ export class RadioButton extends Control {
         return "RadioButton";
         return "RadioButton";
     }
     }
 
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
         context.save();
 
 
         this._applyStates(context);
         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();
         context.restore();
     }
     }

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

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

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

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

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

@@ -0,0 +1,74 @@
+import { Measure } from "../../measure";
+import { Container } from "../container";
+import { ValueAndUnit } from "../../valueAndUnit";
+
+/**
+ * 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._currentMeasure.left  < 0) {
+                child._currentMeasure.left = this._currentMeasure.left;
+            }
+
+            if (child._currentMeasure.top  < 0) {
+                child._currentMeasure.top = this._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;
+            console.log(maxWidth);
+        }
+
+        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 { TextBlock } from "./textBlock";
 import { Checkbox } from "./checkbox";
 import { Checkbox } from "./checkbox";
 import { RadioButton } from "./radioButton";
 import { RadioButton } from "./radioButton";
-import { Slider } from "./slider";
+import { Slider } from "./sliders/slider";
 import { Container } from "./container";
 import { Container } from "./container";
 
 
 /** Class used to create a RadioGroup
 /** 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";
 import { Observable, Vector2 } from "babylonjs";
 
 
 /**
 /**
@@ -255,7 +255,8 @@ export class BaseSlider extends Control {
     // Events
     // Events
     private _pointerIsDown = false;
     private _pointerIsDown = false;
 
 
-    private _updateValueFromPointer(x: number, y: number): void {
+    /** @hidden */
+    protected _updateValueFromPointer(x: number, y: number): void {
         if (this.rotation != 0) {
         if (this.rotation != 0) {
             this._invertTransformMatrix.transformCoordinates(x, y, this._transformedPosition);
             this._invertTransformMatrix.transformCoordinates(x, y, this._transformedPosition);
             x = this._transformedPosition.x;
             x = this._transformedPosition.x;

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

@@ -1,6 +1,6 @@
 import { BaseSlider } from "./baseSlider";
 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
  * Class used to create slider controls based on images
@@ -100,60 +100,62 @@ export class ImageBasedSlider extends BaseSlider {
         return "ImageBasedSlider";
         return "ImageBasedSlider";
     }
     }
 
 
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
         context.save();
 
 
         this._applyStates(context);
         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.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 {
                 } 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 {
                 } 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();
         context.restore();

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

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

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

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

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

@@ -10,7 +10,6 @@ export class StackPanel extends Container {
     private _manualWidth = false;
     private _manualWidth = false;
     private _manualHeight = false;
     private _manualHeight = false;
     private _doNotTrackManualChanges = false;
     private _doNotTrackManualChanges = false;
-    private _tempMeasureStore = Measure.Empty();
 
 
     /** Gets or sets a boolean indicating if the stack panel is vertical or horizontal*/
     /** Gets or sets a boolean indicating if the stack panel is vertical or horizontal*/
     public get isVertical(): boolean {
     public get isVertical(): boolean {
@@ -82,13 +81,41 @@ export class StackPanel extends Container {
         return "StackPanel";
         return "StackPanel";
     }
     }
 
 
+    /** @hidden */
     protected _preMeasure(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
     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 stackWidth = 0;
         var stackHeight = 0;
         var stackHeight = 0;
         for (var child of this._children) {
         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) {
             if (this._isVertical) {
                 child.top = stackHeight + "px";
                 child.top = stackHeight + "px";
@@ -96,25 +123,21 @@ export class StackPanel extends Container {
                     child._markAsDirty();
                     child._markAsDirty();
                 }
                 }
                 child._top.ignoreAdaptiveScaling = true;
                 child._top.ignoreAdaptiveScaling = true;
-                stackHeight += child._currentMeasure.height;
+                stackHeight += child._currentMeasure.height + child.paddingTopInPixels;
                 if (child._currentMeasure.width > stackWidth) {
                 if (child._currentMeasure.width > stackWidth) {
                     stackWidth = child._currentMeasure.width;
                     stackWidth = child._currentMeasure.width;
                 }
                 }
-                child.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
             } else {
             } else {
                 child.left = stackWidth + "px";
                 child.left = stackWidth + "px";
                 if (!child._left.ignoreAdaptiveScaling) {
                 if (!child._left.ignoreAdaptiveScaling) {
                     child._markAsDirty();
                     child._markAsDirty();
                 }
                 }
                 child._left.ignoreAdaptiveScaling = true;
                 child._left.ignoreAdaptiveScaling = true;
-                stackWidth += child._currentMeasure.width;
+                stackWidth += child._currentMeasure.width + child.paddingLeftInPixels;
                 if (child._currentMeasure.height > stackHeight) {
                 if (child._currentMeasure.height > stackHeight) {
                     stackHeight = child._currentMeasure.height;
                     stackHeight = child._currentMeasure.height;
                 }
                 }
-                child.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
             }
             }
-
-            child._currentMeasure.copyFrom(this._tempMeasureStore);
         }
         }
 
 
         this._doNotTrackManualChanges = true;
         this._doNotTrackManualChanges = true;
@@ -125,21 +148,18 @@ export class StackPanel extends Container {
         let panelWidthChanged = false;
         let panelWidthChanged = false;
         let panelHeightChanged = 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";
             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";
             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) {
         if (panelHeightChanged) {
             this._height.ignoreAdaptiveScaling = true;
             this._height.ignoreAdaptiveScaling = true;
         }
         }
@@ -151,9 +171,9 @@ export class StackPanel extends Container {
         this._doNotTrackManualChanges = false;
         this._doNotTrackManualChanges = false;
 
 
         if (panelWidthChanged || panelHeightChanged) {
         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
      * Gets or sets an boolean indicating that the TextBlock will be resized to fit container
      */
      */
     public set resizeToFit(value: boolean) {
     public set resizeToFit(value: boolean) {
+        if (this._resizeToFit === value) {
+            return;
+        }
         this._resizeToFit = value;
         this._resizeToFit = value;
 
 
         if (this._resizeToFit) {
         if (this._resizeToFit) {
             this._width.ignoreAdaptiveScaling = true;
             this._width.ignoreAdaptiveScaling = true;
             this._height.ignoreAdaptiveScaling = true;
             this._height.ignoreAdaptiveScaling = true;
         }
         }
+
+        this._markAsDirty();
     }
     }
 
 
     /**
     /**
@@ -221,6 +226,44 @@ export class TextBlock extends Control {
         return "TextBlock";
         return "TextBlock";
     }
     }
 
 
+    protected _processMeasures(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+        if (!this._fontOffset) {
+            this._fontOffset = Control._GetFontOffset(context.font);
+        }
+
+        // Prepare lines
+        this._lines = this._breakLines(this._currentMeasure.width, context);
+        this.onLinesReadyObservable.notifyObservers(this);
+
+        let maxLineWidth: number = 0;
+
+        for (let i = 0; i < this._lines.length; i++) {
+            const line = this._lines[i];
+
+            if (line.width > maxLineWidth) {
+                maxLineWidth = line.width;
+            }
+        }
+
+        if (this._resizeToFit) {
+            if (this._textWrapping === TextWrapping.Clip) {
+                let newWidth = this.paddingLeftInPixels + this.paddingRightInPixels + maxLineWidth;
+                if (newWidth !== this._width.internalValue) {
+                    this._width.updateInPlace(newWidth, ValueAndUnit.UNITMODE_PIXEL);
+                    this._isDirty = true;
+                }
+            }
+            let newHeight = this.paddingTopInPixels + this.paddingBottomInPixels + this._fontOffset.height * this._lines.length;
+
+            if (newHeight !== this._height.internalValue) {
+                this._height.updateInPlace(newHeight, ValueAndUnit.UNITMODE_PIXEL);
+                this._isDirty = true;
+            }
+        }
+
+        super._processMeasures(parentMeasure, context);
+    }
+
     private _drawText(text: string, textWidth: number, y: number, context: CanvasRenderingContext2D): void {
     private _drawText(text: string, textWidth: number, y: number, context: CanvasRenderingContext2D): void {
         var width = this._currentMeasure.width;
         var width = this._currentMeasure.width;
         var x = 0;
         var x = 0;
@@ -250,15 +293,14 @@ export class TextBlock extends Control {
     }
     }
 
 
     /** @hidden */
     /** @hidden */
-    public _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
+    public _draw(context: CanvasRenderingContext2D): void {
         context.save();
         context.save();
 
 
         this._applyStates(context);
         this._applyStates(context);
 
 
-        if (this._processMeasures(parentMeasure, context)) {
-            // Render lines
-            this._renderLines(context);
-        }
+        // Render lines
+        this._renderLines(context);
+
         context.restore();
         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[] {
     protected _breakLines(refWidth: number, context: CanvasRenderingContext2D): object[] {
         var lines = [];
         var lines = [];
         var _lines = this.text.split("\n");
         var _lines = this.text.split("\n");
 
 
-        if (this._textWrapping === TextWrapping.Ellipsis && !this._resizeToFit) {
+        if (this._textWrapping === TextWrapping.Ellipsis) {
             for (var _line of _lines) {
             for (var _line of _lines) {
                 lines.push(this._parseLineEllipsis(_line, refWidth, context));
                 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) {
             for (var _line of _lines) {
                 lines.push(...this._parseLineWordWrap(_line, refWidth, context));
                 lines.push(...this._parseLineWordWrap(_line, refWidth, context));
             }
             }
@@ -342,10 +379,6 @@ export class TextBlock extends Control {
 
 
     protected _renderLines(context: CanvasRenderingContext2D): void {
     protected _renderLines(context: CanvasRenderingContext2D): void {
         var height = this._currentMeasure.height;
         var height = this._currentMeasure.height;
-
-        if (!this._fontOffset) {
-            this._fontOffset = Control._GetFontOffset(context.font);
-        }
         var rootY = 0;
         var rootY = 0;
         switch (this._textVerticalAlignment) {
         switch (this._textVerticalAlignment) {
             case Control.VERTICAL_ALIGNMENT_TOP:
             case Control.VERTICAL_ALIGNMENT_TOP:
@@ -361,8 +394,6 @@ export class TextBlock extends Control {
 
 
         rootY += this._currentMeasure.top;
         rootY += this._currentMeasure.top;
 
 
-        var maxLineWidth: number = 0;
-
         for (let i = 0; i < this._lines.length; i++) {
         for (let i = 0; i < this._lines.length; i++) {
             const line = this._lines[i];
             const line = this._lines[i];
 
 
@@ -377,13 +408,6 @@ export class TextBlock extends Control {
 
 
             this._drawText(line.text, line.width, rootY, context);
             this._drawText(line.text, line.width, rootY, context);
             rootY += this._fontOffset.height;
             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
      * Gets the value accordingly to its unit
      * @param host  defines the root host
      * @param host  defines the root host
      * @returns the value
      * @returns the value

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

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

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

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

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

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

+ 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) {
                 if (!this.props.onSelectionChangedObservable) {
                     return;
                     return;
                 }
                 }
+
+                if (control.getClassName() === "ScrollViewerWindow") {
+                    control = control.getAscendantOfClass("ScrollViewer")!;
+                }
+
                 this.props.onSelectionChangedObservable.notifyObservers(control);
                 this.props.onSelectionChangedObservable.notifyObservers(control);
             });
             });
         }
         }