Skip to content

Commit 45fec01

Browse files
committed
splitview: add orthogonal size
grid: combine layout and orthogonalLayout fixes microsoft#78173
1 parent c23b596 commit 45fec01

3 files changed

Lines changed: 98 additions & 58 deletions

File tree

src/vs/base/browser/ui/grid/gridview.ts

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ export class LayoutController implements ILayoutController {
7676
constructor(public isLayoutEnabled: boolean) { }
7777
}
7878

79+
export class MultiplexLayoutController implements ILayoutController {
80+
get isLayoutEnabled(): boolean { return this.layoutControllers.every(l => l.isLayoutEnabled); }
81+
constructor(private layoutControllers: ILayoutController[]) { }
82+
}
83+
7984
export interface IGridViewOptions {
8085
readonly styles?: IGridViewStyles;
8186
readonly proportionalLayout?: boolean; // default true
@@ -170,6 +175,7 @@ class BranchNode implements ISplitView, IDisposable {
170175

171176
constructor(
172177
readonly orientation: Orientation,
178+
readonly layoutController: ILayoutController,
173179
styles: IGridViewStyles,
174180
readonly proportionalLayout: boolean,
175181
size: number = 0,
@@ -181,7 +187,7 @@ class BranchNode implements ISplitView, IDisposable {
181187

182188
this.element = $('.monaco-grid-branch-node');
183189
this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout });
184-
this.splitview.layout(size);
190+
this.splitview.layout(size, orthogonalSize);
185191

186192
const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]);
187193
this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset);
@@ -198,12 +204,19 @@ class BranchNode implements ISplitView, IDisposable {
198204
}
199205
}
200206

201-
layout(size: number): void {
202-
this._orthogonalSize = size;
207+
layout(size: number, orthogonalSize: number | undefined): void {
208+
if (!this.layoutController.isLayoutEnabled) {
209+
return;
210+
}
203211

204-
for (const child of this.children) {
205-
child.orthogonalLayout(size);
212+
if (typeof orthogonalSize !== 'number') {
213+
throw new Error('Invalid state');
206214
}
215+
216+
this._size = orthogonalSize;
217+
this._orthogonalSize = size;
218+
219+
this.splitview.layout(orthogonalSize, size);
207220
}
208221

209222
setVisible(visible: boolean): void {
@@ -212,11 +225,6 @@ class BranchNode implements ISplitView, IDisposable {
212225
}
213226
}
214227

215-
orthogonalLayout(size: number): void {
216-
this._size = size;
217-
this.splitview.layout(size);
218-
}
219-
220228
addChild(node: Node, size: number | Sizing, index: number): void {
221229
if (index < 0 || index > this.children.length) {
222230
throw new Error('Invalid index');
@@ -539,12 +547,18 @@ class LeafNode implements ISplitView, IDisposable {
539547
// noop
540548
}
541549

542-
layout(size: number): void {
543-
this._size = size;
550+
layout(size: number, orthogonalSize: number | undefined): void {
551+
if (!this.layoutController.isLayoutEnabled) {
552+
return;
553+
}
544554

545-
if (this.layoutController.isLayoutEnabled) {
546-
this.view.layout(this.width, this.height, orthogonal(this.orientation));
555+
if (typeof orthogonalSize !== 'number') {
556+
throw new Error('Invalid state');
547557
}
558+
559+
this._size = size;
560+
this._orthogonalSize = orthogonalSize;
561+
this.view.layout(this.width, this.height, orthogonal(this.orientation));
548562
}
549563

550564
setVisible(visible: boolean): void {
@@ -553,22 +567,14 @@ class LeafNode implements ISplitView, IDisposable {
553567
}
554568
}
555569

556-
orthogonalLayout(size: number): void {
557-
this._orthogonalSize = size;
558-
559-
if (this.layoutController.isLayoutEnabled) {
560-
this.view.layout(this.width, this.height, orthogonal(this.orientation));
561-
}
562-
}
563-
564570
dispose(): void { }
565571
}
566572

567573
type Node = BranchNode | LeafNode;
568574

569575
function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number): T {
570576
if (node instanceof BranchNode) {
571-
const result = new BranchNode(orthogonal(node.orientation), node.styles, node.proportionalLayout, size, orthogonalSize);
577+
const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize);
572578

573579
let totalSize = 0;
574580

@@ -589,7 +595,7 @@ function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number)
589595

590596
return result as T;
591597
} else {
592-
return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), (node as LeafNode).layoutController, orthogonalSize) as T;
598+
return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), node.layoutController, orthogonalSize) as T;
593599
}
594600
}
595601

@@ -634,8 +640,9 @@ export class GridView implements IDisposable {
634640

635641
const { size, orthogonalSize } = this._root;
636642
this.root = flipNode(this._root, orthogonalSize, size);
637-
this.root.layout(size);
638-
this.root.orthogonalLayout(orthogonalSize);
643+
this.root.layout(size, orthogonalSize);
644+
// this.root.layout(size);
645+
// this.root.orthogonalLayout(orthogonalSize);
639646
}
640647

641648
get width(): number { return this.root.width; }
@@ -649,14 +656,25 @@ export class GridView implements IDisposable {
649656
private _onDidChange = new Relay<IViewSize | undefined>();
650657
readonly onDidChange = this._onDidChange.event;
651658

659+
/**
660+
* The first layout controller makes sure layout only propagates
661+
* to the views after the very first call to gridview.layout()
662+
*/
663+
private firstLayoutController: LayoutController;
652664
private layoutController: LayoutController;
653665

654666
constructor(options: IGridViewOptions = {}) {
655667
this.element = $('.monaco-grid-view');
656668
this.styles = options.styles || defaultStyles;
657669
this.proportionalLayout = typeof options.proportionalLayout !== 'undefined' ? !!options.proportionalLayout : true;
658-
this.root = new BranchNode(Orientation.VERTICAL, this.styles, this.proportionalLayout);
659-
this.layoutController = options.layoutController || new LayoutController(true);
670+
671+
this.firstLayoutController = new LayoutController(false);
672+
this.layoutController = new MultiplexLayoutController([
673+
this.firstLayoutController,
674+
...(options.layoutController ? [options.layoutController] : [])
675+
]);
676+
677+
this.root = new BranchNode(Orientation.VERTICAL, this.layoutController, this.styles, this.proportionalLayout);
660678
}
661679

662680
style(styles: IGridViewStyles): void {
@@ -665,9 +683,10 @@ export class GridView implements IDisposable {
665683
}
666684

667685
layout(width: number, height: number): void {
686+
this.firstLayoutController.isLayoutEnabled = true;
687+
668688
const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height];
669-
this.root.layout(size);
670-
this.root.orthogonalLayout(orthogonalSize);
689+
this.root.layout(size, orthogonalSize);
671690
}
672691

673692
addView(view: IView, size: number | Sizing, location: number[]): void {
@@ -694,9 +713,8 @@ export class GridView implements IDisposable {
694713

695714
grandParent.removeChild(parentIndex);
696715

697-
const newParent = new BranchNode(parent.orientation, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize);
716+
const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize);
698717
grandParent.addChild(newParent, parent.size, parentIndex);
699-
newParent.orthogonalLayout(parent.orthogonalSize);
700718

701719
const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size);
702720
newParent.addChild(newSibling, newSiblingSize, 0);
@@ -827,9 +845,6 @@ export class GridView implements IDisposable {
827845

828846
fromParent.addChild(toNode, fromSize, fromIndex);
829847
toParent.addChild(fromNode, toSize, toIndex);
830-
831-
fromParent.layout(fromParent.orthogonalSize);
832-
toParent.layout(toParent.orthogonalSize);
833848
}
834849
}
835850

src/vs/base/browser/ui/splitview/splitview.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ const defaultStyles: ISplitViewStyles = {
2424
};
2525

2626
export interface ISplitViewOptions {
27-
orientation?: Orientation; // default Orientation.VERTICAL
28-
styles?: ISplitViewStyles;
29-
orthogonalStartSash?: Sash;
30-
orthogonalEndSash?: Sash;
31-
inverseAltBehavior?: boolean;
32-
proportionalLayout?: boolean; // default true
27+
readonly orientation?: Orientation; // default Orientation.VERTICAL
28+
readonly styles?: ISplitViewStyles;
29+
readonly orthogonalStartSash?: Sash;
30+
readonly orthogonalEndSash?: Sash;
31+
readonly inverseAltBehavior?: boolean;
32+
readonly proportionalLayout?: boolean; // default true
3333
}
3434

3535
/**
@@ -48,7 +48,7 @@ export interface IView {
4848
readonly onDidChange: Event<number | undefined>;
4949
readonly priority?: LayoutPriority;
5050
readonly snap?: boolean;
51-
layout(size: number, orientation: Orientation): void;
51+
layout(size: number, orthogonalSize: number | undefined): void;
5252
setVisible?(visible: boolean): void;
5353
}
5454

@@ -125,13 +125,13 @@ abstract class ViewItem {
125125
dom.addClass(container, 'visible');
126126
}
127127

128-
layout(): void {
128+
layout(_orthogonalSize: number | undefined): void {
129129
this.container.scrollTop = 0;
130130
this.container.scrollLeft = 0;
131131
}
132132

133-
layoutView(orientation: Orientation): void {
134-
this.view.layout(this.size, orientation);
133+
layoutView(orthogonalSize: number | undefined): void {
134+
this.view.layout(this.size, orthogonalSize);
135135
}
136136

137137
dispose(): IView {
@@ -142,19 +142,19 @@ abstract class ViewItem {
142142

143143
class VerticalViewItem extends ViewItem {
144144

145-
layout(): void {
146-
super.layout();
145+
layout(orthogonalSize: number | undefined): void {
146+
super.layout(orthogonalSize);
147147
this.container.style.height = `${this.size}px`;
148-
this.layoutView(Orientation.VERTICAL);
148+
this.layoutView(orthogonalSize);
149149
}
150150
}
151151

152152
class HorizontalViewItem extends ViewItem {
153153

154-
layout(): void {
155-
super.layout();
154+
layout(orthogonalSize: number | undefined): void {
155+
super.layout(orthogonalSize);
156156
this.container.style.width = `${this.size}px`;
157-
this.layoutView(Orientation.HORIZONTAL);
157+
this.layoutView(orthogonalSize);
158158
}
159159
}
160160

@@ -205,6 +205,7 @@ export class SplitView extends Disposable {
205205
private sashContainer: HTMLElement;
206206
private viewContainer: HTMLElement;
207207
private size = 0;
208+
private orthogonalSize: number | undefined;
208209
private contentSize = 0;
209210
private proportions: undefined | number[] = undefined;
210211
private viewItems: ViewItem[] = [];
@@ -475,9 +476,10 @@ export class SplitView extends Disposable {
475476
return viewItem.cachedVisibleSize;
476477
}
477478

478-
layout(size: number): void {
479+
layout(size: number, orthogonalSize?: number): void {
479480
const previousSize = Math.max(this.size, this.contentSize);
480481
this.size = size;
482+
this.orthogonalSize = orthogonalSize;
481483

482484
if (!this.proportions) {
483485
const indexes = range(this.viewItems.length);
@@ -820,7 +822,7 @@ export class SplitView extends Disposable {
820822
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
821823

822824
// Layout views
823-
this.viewItems.forEach(item => item.layout());
825+
this.viewItems.forEach(item => item.layout(this.orthogonalSize));
824826

825827
// Layout sashes
826828
this.sashItems.forEach(item => item.sash.layout());

src/vs/base/test/browser/ui/splitview/splitview.test.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as assert from 'assert';
77
import { Emitter } from 'vs/base/common/event';
8-
import { SplitView, IView, Orientation, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
8+
import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
99
import { Sash, SashState } from 'vs/base/browser/ui/sash/sash';
1010

1111
class TestView implements IView {
@@ -27,7 +27,9 @@ class TestView implements IView {
2727

2828
private _size = 0;
2929
get size(): number { return this._size; }
30-
private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>();
30+
private _orthogonalSize: number | undefined = 0;
31+
get orthogonalSize(): number | undefined { return this._orthogonalSize; }
32+
private _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>();
3133
readonly onDidLayout = this._onDidLayout.event;
3234

3335
private _onDidFocus = new Emitter<void>();
@@ -41,9 +43,10 @@ class TestView implements IView {
4143
assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
4244
}
4345

44-
layout(size: number, orientation: Orientation): void {
46+
layout(size: number, orthogonalSize: number | undefined): void {
4547
this._size = size;
46-
this._onDidLayout.fire({ size, orientation });
48+
this._orthogonalSize = orthogonalSize;
49+
this._onDidLayout.fire({ size, orthogonalSize });
4750
}
4851

4952
focus(): void {
@@ -523,4 +526,24 @@ suite('Splitview', () => {
523526
view2.dispose();
524527
view1.dispose();
525528
});
526-
});
529+
530+
test('orthogonal size propagates to views', () => {
531+
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
532+
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
533+
const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low);
534+
const splitview = new SplitView(container, { proportionalLayout: false });
535+
splitview.layout(200);
536+
537+
splitview.addView(view1, Sizing.Distribute);
538+
splitview.addView(view2, Sizing.Distribute);
539+
splitview.addView(view3, Sizing.Distribute);
540+
541+
splitview.layout(200, 100);
542+
assert.deepEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]);
543+
544+
splitview.dispose();
545+
view3.dispose();
546+
view2.dispose();
547+
view1.dispose();
548+
});
549+
});

0 commit comments

Comments
 (0)