Browse Source

Speed up rendering by frozing child controls

Popov72 5 years ago
parent
commit
8fff80a7cc

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

@@ -12,7 +12,7 @@ import { _TypeStore } from 'babylonjs/Misc/typeStore';
  */
 export class Container extends Control {
     /** @hidden */
-    protected _children = new Array<Control>();
+    public _children = new Array<Control>();
     /** @hidden */
     protected _measureForChildren = Measure.Empty();
     /** @hidden */

+ 3 - 0
gui/src/2D/controls/control.ts

@@ -105,6 +105,9 @@ export class Control {
     protected _rebuildLayout = false;
 
     /** @hidden */
+    public _customData: any = {};
+
+    /** @hidden */
     public _isClipped = false;
 
     /** @hidden */

+ 75 - 30
gui/src/2D/controls/scrollViewers/scrollViewer.ts

@@ -29,8 +29,6 @@ export class ScrollViewer extends Rectangle {
     private _barImage: Image;
     private _barBackgroundImage: Image;
     private _barSize: number = 20;
-    private _endLeft: number;
-    private _endTop: number;
     private _window: _ScrollViewerWindow;
     private _pointerIsOver: Boolean = false;
     private _wheelPrecision: number = 0.05;
@@ -93,6 +91,48 @@ export class ScrollViewer extends Rectangle {
     }
 
     /**
+     * Freezes or unfreezes the controls in the window.
+     * When controls are frozen, the scroll viewer can render a lot more quickly but updates to positions/sizes of controls
+     * are not taken into account. If you want to change positions/sizes, unfreeze, perform the changes then freeze again
+     */
+    public get freezeControls(): boolean {
+        return this._window.freezeControls;
+    }
+
+    public set freezeControls(value: boolean) {
+        this._window.freezeControls = value;
+    }
+
+    private _forceHorizontalBar: boolean = false;
+    private _forceVerticalBar: boolean = false;
+
+    /**
+     * Forces the horizontal scroll bar to be displayed
+     */
+    public get forceHorizontalBar(): boolean {
+        return this._forceHorizontalBar;
+    }
+
+    public set forceHorizontalBar(value: boolean) {
+        this._grid.setRowDefinition(1, value ? this._barSize : 0, true);
+        this._horizontalBar.isVisible = value;
+        this._forceHorizontalBar = value;
+    }
+
+    /**
+     * Forces the vertical scroll bar to be displayed
+     */
+    public get forceVerticalBar(): boolean {
+        return this._forceVerticalBar;
+    }
+
+    public set forceVerticalBar(value: boolean) {
+        this._grid.setColumnDefinition(1, value ? this._barSize : 0, true);
+        this._verticalBar.isVisible = value;
+        this._forceVerticalBar = value;
+    }
+
+    /**
     * Creates a new ScrollViewer
     * @param name of ScrollViewer
     */
@@ -125,7 +165,7 @@ export class ScrollViewer extends Rectangle {
             this._verticalBar = new ScrollBar();
         }
 
-        this._window = new _ScrollViewerWindow();
+        this._window = new _ScrollViewerWindow("scrollViewer_window");
         this._window.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
         this._window.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
 
@@ -173,8 +213,8 @@ export class ScrollViewer extends Rectangle {
     }
 
     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._window.parentClientWidth = this._currentMeasure.width - (this._verticalBar.isVisible || this.forceVerticalBar ? this._barSize : 0) - 2 * this.thickness;
+        this._window.parentClientHeight = this._currentMeasure.height - (this._horizontalBar.isVisible || this.forceHorizontalBar ? this._barSize : 0) - 2 * this.thickness;
 
         this._clientWidth = this._window.parentClientWidth;
         this._clientHeight = this._window.parentClientHeight;
@@ -385,51 +425,61 @@ export class ScrollViewer extends Rectangle {
         vb.backgroundImage = value;
     }
 
+    private _setWindowPosition(): void {
+        let windowContentsWidth = this._window._currentMeasure.width;
+        let windowContentsHeight = this._window._currentMeasure.height;
+
+        const _endLeft = this._clientWidth - windowContentsWidth;
+        const _endTop = this._clientHeight - windowContentsHeight;
+
+        const newLeft = this._horizontalBar.value * _endLeft + "px";
+        const newTop = this._verticalBar.value * _endTop + "px";
+
+        if (newLeft !== this._window.left) {
+            this._window.left = newLeft;
+            if (!this.freezeControls) {
+                this._rebuildLayout = true;
+            }
+        }
+
+        if (newTop !== this._window.top) {
+            this._window.top = newTop;
+            if (!this.freezeControls) {
+                this._rebuildLayout = true;
+            }
+        }
+    }
+
     /** @hidden */
     private _updateScroller(): void {
         let windowContentsWidth = this._window._currentMeasure.width;
         let windowContentsHeight = this._window._currentMeasure.height;
 
-        if (this._horizontalBar.isVisible && windowContentsWidth <= this._clientWidth) {
+        if (this._horizontalBar.isVisible && windowContentsWidth <= this._clientWidth && !this.forceHorizontalBar) {
             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) {
+        else if (!this._horizontalBar.isVisible && (windowContentsWidth > this._clientWidth || this.forceHorizontalBar)) {
             this._grid.setRowDefinition(1, this._barSize, true);
             this._horizontalBar.isVisible = true;
             this._rebuildLayout = true;
         }
 
-        if (this._verticalBar.isVisible && windowContentsHeight <= this._clientHeight) {
+        if (this._verticalBar.isVisible && windowContentsHeight <= this._clientHeight && !this.forceVerticalBar) {
             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) {
+        else if (!this._verticalBar.isVisible && (windowContentsHeight > this._clientHeight || this.forceVerticalBar)) {
             this._grid.setColumnDefinition(1, this._barSize, true);
             this._verticalBar.isVisible = true;
             this._rebuildLayout = true;
         }
 
         this._buildClientSizes();
-        this._endLeft = this._clientWidth - windowContentsWidth;
-        this._endTop = this._clientHeight - windowContentsHeight;
-
-        const newLeft = this._horizontalBar.value * this._endLeft + "px";
-        const newTop = this._verticalBar.value * this._endTop + "px";
-
-        if (newLeft !== this._window.left) {
-            this._window.left = newLeft;
-            this._rebuildLayout = true;
-        }
-
-        if (newTop !== this._window.top) {
-            this._window.top = newTop;
-            this._rebuildLayout = true;
-        }
 
         this._horizontalBar.thumbWidth = this._thumbLength * 0.9 * this._clientWidth + "px";
         this._verticalBar.thumbWidth = this._thumbLength *  0.9 * this._clientHeight + "px";
@@ -458,12 +508,7 @@ export class ScrollViewer extends Rectangle {
         barContainer.addControl(barControl);
 
         barControl.onValueChangedObservable.add((value) => {
-            if (rotation > 0) {
-                this._window.top = value * this._endTop + "px";
-            }
-            else {
-                this._window.left = value * this._endLeft + "px";
-            }
+            this._setWindowPosition();
         });
     }
 

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

@@ -11,6 +11,75 @@ export class _ScrollViewerWindow extends Container {
     public parentClientWidth: number;
     public parentClientHeight: number;
 
+    private _freezeControls = false;
+    private _parentMeasure: Measure;
+
+    /**
+     * Freezes or unfreezes controls
+     */
+    public get freezeControls(): boolean {
+        return this._freezeControls;
+    }
+
+    public set freezeControls(value: boolean) {
+        if (this._freezeControls === value) {
+            return;
+        }
+
+        // trigger a full normal layout calculation to be sure all children have their measures up to date
+        this._freezeControls = false;
+
+        var textureSize = this.host.getSize();
+        var renderWidth = textureSize.width;
+        var renderHeight = textureSize.height;
+
+        var context = this.host.getContext();
+
+        var measure = new Measure(0, 0, renderWidth, renderHeight);
+
+        Control.numLayoutCalls = 0;
+
+        this.host._rootContainer._layout(measure, context);
+
+        // in freeze mode, prepare children measures accordingly
+        if (value) {
+            this._updateMeasures();
+        }
+
+        this._freezeControls = value;
+
+        this.host.markAsDirty(); // redraw with the (new) current settings
+    }
+
+    // reset left and top measures for the window and all its children
+    private _updateMeasures(): void {
+        let left = this.leftInPixels | 0,
+            top = this.topInPixels | 0;
+
+        this._measureForChildren.left -= left;
+        this._measureForChildren.top -= top;
+        this._currentMeasure.left -= left;
+        this._currentMeasure.top -= top;
+
+        this._updateChildrenMeasures(this._children, left, top);
+    }
+
+    private _updateChildrenMeasures(children: Control[], left: number, top: number): void {
+        for (let i = 0; i < children.length; ++i) {
+            let child = children[i];
+
+            child._currentMeasure.left -= left;
+            child._currentMeasure.top -= top;
+
+            child._customData._origLeft = child._currentMeasure.left; // save the original left and top values for each child
+            child._customData._origTop = child._currentMeasure.top;
+
+            if (child instanceof Container && child._children.length > 0) {
+                this._updateChildrenMeasures(child._children, left, top);
+            }
+        }
+    }
+
     /**
     * Creates a new ScrollViewerWindow
     * @param name of ScrollViewerWindow
@@ -27,6 +96,8 @@ export class _ScrollViewerWindow extends Container {
     protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
         super._additionalProcessing(parentMeasure, context);
 
+        this._parentMeasure = parentMeasure;
+
         this._measureForChildren.left = this._currentMeasure.left;
         this._measureForChildren.top = this._currentMeasure.top;
 
@@ -34,7 +105,62 @@ export class _ScrollViewerWindow extends Container {
         this._measureForChildren.height = parentMeasure.height;
     }
 
+    /** @hidden */
+    public _layout(parentMeasure: Measure, context: CanvasRenderingContext2D): boolean {
+        if (this._freezeControls) {
+            this.invalidateRect(); // will trigger a redraw of the window
+            return false;
+        }
+
+        return super._layout(parentMeasure, context);
+    }
+
+    private _scrollChildren(children: Control[], left: number, top: number): void {
+        for (let i = 0; i < children.length; ++i) {
+            let child = children[i];
+
+            child._currentMeasure.left = child._customData._origLeft + left;
+            child._currentMeasure.top = child._customData._origTop + top;
+            child._isClipped = false; // clipping will be handled by _draw and the call to _intersectsRect()
+
+            if (child instanceof Container && child._children.length > 0) {
+                this._scrollChildren(child._children, left, top);
+            }
+        }
+    }
+
+    /** @hidden */
+    public _draw(context: CanvasRenderingContext2D, invalidatedRectangle?: Measure): void {
+        if (!this._freezeControls) {
+            super._draw(context, invalidatedRectangle);
+            return;
+        }
+
+        this._localDraw(context);
+
+        if (this.clipChildren) {
+            this._clipForChildren(context);
+        }
+
+        let left = this.leftInPixels,
+            top = this.topInPixels;
+
+        this._scrollChildren(this._children, left, top);
+
+        for (var child of this._children) {
+            if (!child._intersectsRect(this._parentMeasure)) {
+                continue;
+            }
+            child._render(context, this._parentMeasure);
+        }
+    }
+
     protected _postMeasure(): void {
+        if (this._freezeControls) {
+            super._postMeasure();
+            return;
+        }
+
         var maxWidth = this.parentClientWidth;
         var maxHeight = this.parentClientHeight;
         for (var child of this.children) {