diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 5cefe23c54952..38d1131affe90 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -76,6 +76,11 @@ export class LayoutController implements ILayoutController { constructor(public isLayoutEnabled: boolean) { } } +export class MultiplexLayoutController implements ILayoutController { + get isLayoutEnabled(): boolean { return this.layoutControllers.every(l => l.isLayoutEnabled); } + constructor(private layoutControllers: ILayoutController[]) { } +} + export interface IGridViewOptions { readonly styles?: IGridViewStyles; readonly proportionalLayout?: boolean; // default true @@ -170,6 +175,7 @@ class BranchNode implements ISplitView, IDisposable { constructor( readonly orientation: Orientation, + readonly layoutController: ILayoutController, styles: IGridViewStyles, readonly proportionalLayout: boolean, size: number = 0, @@ -181,7 +187,7 @@ class BranchNode implements ISplitView, IDisposable { this.element = $('.monaco-grid-branch-node'); this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout }); - this.splitview.layout(size); + this.splitview.layout(size, orthogonalSize); const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]); this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset); @@ -198,12 +204,19 @@ class BranchNode implements ISplitView, IDisposable { } } - layout(size: number): void { - this._orthogonalSize = size; + layout(size: number, orthogonalSize: number | undefined): void { + if (!this.layoutController.isLayoutEnabled) { + return; + } - for (const child of this.children) { - child.orthogonalLayout(size); + if (typeof orthogonalSize !== 'number') { + throw new Error('Invalid state'); } + + this._size = orthogonalSize; + this._orthogonalSize = size; + + this.splitview.layout(orthogonalSize, size); } setVisible(visible: boolean): void { @@ -212,11 +225,6 @@ class BranchNode implements ISplitView, IDisposable { } } - orthogonalLayout(size: number): void { - this._size = size; - this.splitview.layout(size); - } - addChild(node: Node, size: number | Sizing, index: number): void { if (index < 0 || index > this.children.length) { throw new Error('Invalid index'); @@ -539,12 +547,18 @@ class LeafNode implements ISplitView, IDisposable { // noop } - layout(size: number): void { - this._size = size; + layout(size: number, orthogonalSize: number | undefined): void { + if (!this.layoutController.isLayoutEnabled) { + return; + } - if (this.layoutController.isLayoutEnabled) { - this.view.layout(this.width, this.height, orthogonal(this.orientation)); + if (typeof orthogonalSize !== 'number') { + throw new Error('Invalid state'); } + + this._size = size; + this._orthogonalSize = orthogonalSize; + this.view.layout(this.width, this.height, orthogonal(this.orientation)); } setVisible(visible: boolean): void { @@ -553,14 +567,6 @@ class LeafNode implements ISplitView, IDisposable { } } - orthogonalLayout(size: number): void { - this._orthogonalSize = size; - - if (this.layoutController.isLayoutEnabled) { - this.view.layout(this.width, this.height, orthogonal(this.orientation)); - } - } - dispose(): void { } } @@ -568,7 +574,7 @@ type Node = BranchNode | LeafNode; function flipNode(node: T, size: number, orthogonalSize: number): T { if (node instanceof BranchNode) { - const result = new BranchNode(orthogonal(node.orientation), node.styles, node.proportionalLayout, size, orthogonalSize); + const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize); let totalSize = 0; @@ -589,7 +595,7 @@ function flipNode(node: T, size: number, orthogonalSize: number) return result as T; } else { - return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), (node as LeafNode).layoutController, orthogonalSize) as T; + return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), node.layoutController, orthogonalSize) as T; } } @@ -634,8 +640,9 @@ export class GridView implements IDisposable { const { size, orthogonalSize } = this._root; this.root = flipNode(this._root, orthogonalSize, size); - this.root.layout(size); - this.root.orthogonalLayout(orthogonalSize); + this.root.layout(size, orthogonalSize); + // this.root.layout(size); + // this.root.orthogonalLayout(orthogonalSize); } get width(): number { return this.root.width; } @@ -649,14 +656,25 @@ export class GridView implements IDisposable { private _onDidChange = new Relay(); readonly onDidChange = this._onDidChange.event; + /** + * The first layout controller makes sure layout only propagates + * to the views after the very first call to gridview.layout() + */ + private firstLayoutController: LayoutController; private layoutController: LayoutController; constructor(options: IGridViewOptions = {}) { this.element = $('.monaco-grid-view'); this.styles = options.styles || defaultStyles; this.proportionalLayout = typeof options.proportionalLayout !== 'undefined' ? !!options.proportionalLayout : true; - this.root = new BranchNode(Orientation.VERTICAL, this.styles, this.proportionalLayout); - this.layoutController = options.layoutController || new LayoutController(true); + + this.firstLayoutController = new LayoutController(false); + this.layoutController = new MultiplexLayoutController([ + this.firstLayoutController, + ...(options.layoutController ? [options.layoutController] : []) + ]); + + this.root = new BranchNode(Orientation.VERTICAL, this.layoutController, this.styles, this.proportionalLayout); } style(styles: IGridViewStyles): void { @@ -665,9 +683,10 @@ export class GridView implements IDisposable { } layout(width: number, height: number): void { + this.firstLayoutController.isLayoutEnabled = true; + const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height]; - this.root.layout(size); - this.root.orthogonalLayout(orthogonalSize); + this.root.layout(size, orthogonalSize); } addView(view: IView, size: number | Sizing, location: number[]): void { @@ -694,9 +713,8 @@ export class GridView implements IDisposable { grandParent.removeChild(parentIndex); - const newParent = new BranchNode(parent.orientation, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); + const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); grandParent.addChild(newParent, parent.size, parentIndex); - newParent.orthogonalLayout(parent.orthogonalSize); const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size); newParent.addChild(newSibling, newSiblingSize, 0); @@ -827,9 +845,6 @@ export class GridView implements IDisposable { fromParent.addChild(toNode, fromSize, fromIndex); toParent.addChild(fromNode, toSize, toIndex); - - fromParent.layout(fromParent.orthogonalSize); - toParent.layout(toParent.orthogonalSize); } } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 02c583d1e069f..b5102c9f9b304 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -24,12 +24,12 @@ const defaultStyles: ISplitViewStyles = { }; export interface ISplitViewOptions { - orientation?: Orientation; // default Orientation.VERTICAL - styles?: ISplitViewStyles; - orthogonalStartSash?: Sash; - orthogonalEndSash?: Sash; - inverseAltBehavior?: boolean; - proportionalLayout?: boolean; // default true + readonly orientation?: Orientation; // default Orientation.VERTICAL + readonly styles?: ISplitViewStyles; + readonly orthogonalStartSash?: Sash; + readonly orthogonalEndSash?: Sash; + readonly inverseAltBehavior?: boolean; + readonly proportionalLayout?: boolean; // default true } /** @@ -48,7 +48,7 @@ export interface IView { readonly onDidChange: Event; readonly priority?: LayoutPriority; readonly snap?: boolean; - layout(size: number, orientation: Orientation): void; + layout(size: number, orthogonalSize: number | undefined): void; setVisible?(visible: boolean): void; } @@ -125,13 +125,13 @@ abstract class ViewItem { dom.addClass(container, 'visible'); } - layout(): void { + layout(_orthogonalSize: number | undefined): void { this.container.scrollTop = 0; this.container.scrollLeft = 0; } - layoutView(orientation: Orientation): void { - this.view.layout(this.size, orientation); + layoutView(orthogonalSize: number | undefined): void { + this.view.layout(this.size, orthogonalSize); } dispose(): IView { @@ -142,19 +142,19 @@ abstract class ViewItem { class VerticalViewItem extends ViewItem { - layout(): void { - super.layout(); + layout(orthogonalSize: number | undefined): void { + super.layout(orthogonalSize); this.container.style.height = `${this.size}px`; - this.layoutView(Orientation.VERTICAL); + this.layoutView(orthogonalSize); } } class HorizontalViewItem extends ViewItem { - layout(): void { - super.layout(); + layout(orthogonalSize: number | undefined): void { + super.layout(orthogonalSize); this.container.style.width = `${this.size}px`; - this.layoutView(Orientation.HORIZONTAL); + this.layoutView(orthogonalSize); } } @@ -205,6 +205,7 @@ export class SplitView extends Disposable { private sashContainer: HTMLElement; private viewContainer: HTMLElement; private size = 0; + private orthogonalSize: number | undefined; private contentSize = 0; private proportions: undefined | number[] = undefined; private viewItems: ViewItem[] = []; @@ -475,9 +476,10 @@ export class SplitView extends Disposable { return viewItem.cachedVisibleSize; } - layout(size: number): void { + layout(size: number, orthogonalSize?: number): void { const previousSize = Math.max(this.size, this.contentSize); this.size = size; + this.orthogonalSize = orthogonalSize; if (!this.proportions) { const indexes = range(this.viewItems.length); @@ -820,7 +822,7 @@ export class SplitView extends Disposable { this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); // Layout views - this.viewItems.forEach(item => item.layout()); + this.viewItems.forEach(item => item.layout(this.orthogonalSize)); // Layout sashes this.sashItems.forEach(item => item.sash.layout()); diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 504e665f21a8c..c5e9cfce763d4 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { SplitView, IView, Orientation, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; +import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; import { Sash, SashState } from 'vs/base/browser/ui/sash/sash'; class TestView implements IView { @@ -27,7 +27,9 @@ class TestView implements IView { private _size = 0; get size(): number { return this._size; } - private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>(); + private _orthogonalSize: number | undefined = 0; + get orthogonalSize(): number | undefined { return this._orthogonalSize; } + private _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>(); readonly onDidLayout = this._onDidLayout.event; private _onDidFocus = new Emitter(); @@ -41,9 +43,10 @@ class TestView implements IView { assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size'); } - layout(size: number, orientation: Orientation): void { + layout(size: number, orthogonalSize: number | undefined): void { this._size = size; - this._onDidLayout.fire({ size, orientation }); + this._orthogonalSize = orthogonalSize; + this._onDidLayout.fire({ size, orthogonalSize }); } focus(): void { @@ -523,4 +526,24 @@ suite('Splitview', () => { view2.dispose(); view1.dispose(); }); -}); \ No newline at end of file + + test('orthogonal size propagates to views', () => { + const view1 = new TestView(20, Number.POSITIVE_INFINITY); + const view2 = new TestView(20, Number.POSITIVE_INFINITY); + const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low); + const splitview = new SplitView(container, { proportionalLayout: false }); + splitview.layout(200); + + splitview.addView(view1, Sizing.Distribute); + splitview.addView(view2, Sizing.Distribute); + splitview.addView(view3, Sizing.Distribute); + + splitview.layout(200, 100); + assert.deepEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]); + + splitview.dispose(); + view3.dispose(); + view2.dispose(); + view1.dispose(); + }); +});