mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
splitview: fix layout code
This commit is contained in:
@@ -36,7 +36,6 @@ interface ISashEvent {
|
||||
interface IViewItem {
|
||||
view: IView;
|
||||
size: number;
|
||||
explicitSize: number;
|
||||
container: HTMLElement;
|
||||
disposable: IDisposable;
|
||||
layout(): void;
|
||||
@@ -51,6 +50,8 @@ interface ISashDragState {
|
||||
index: number;
|
||||
start: number;
|
||||
sizes: number[];
|
||||
minDelta: number;
|
||||
maxDelta: number;
|
||||
}
|
||||
|
||||
export class SplitView implements IDisposable {
|
||||
@@ -58,6 +59,7 @@ export class SplitView implements IDisposable {
|
||||
private orientation: Orientation;
|
||||
private el: HTMLElement;
|
||||
private size = 0;
|
||||
private contentSize = 0;
|
||||
private viewItems: IViewItem[] = [];
|
||||
private sashItems: ISashItem[] = [];
|
||||
private sashDragState: ISashDragState;
|
||||
@@ -105,8 +107,7 @@ export class SplitView implements IDisposable {
|
||||
};
|
||||
|
||||
size = Math.round(size);
|
||||
const explicitSize = size;
|
||||
const item: IViewItem = { view, container, explicitSize, size, layout, disposable };
|
||||
const item: IViewItem = { view, container, size, layout, disposable };
|
||||
this.viewItems.splice(index, 0, item);
|
||||
|
||||
// Add sash
|
||||
@@ -129,7 +130,7 @@ export class SplitView implements IDisposable {
|
||||
}
|
||||
|
||||
view.render(container, this.orientation);
|
||||
this.relayoutPreferredSizes();
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
removeView(index: number): void {
|
||||
@@ -148,7 +149,7 @@ export class SplitView implements IDisposable {
|
||||
sashItem.disposable.dispose();
|
||||
}
|
||||
|
||||
this.relayoutPreferredSizes();
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
moveView(from: number, to: number): void {
|
||||
@@ -169,54 +170,82 @@ export class SplitView implements IDisposable {
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
private relayoutPreferredSizes(): void {
|
||||
this.viewItems.forEach(i => i.size = clamp(i.explicitSize, i.view.minimumSize, i.view.maximumSize));
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
private relayout(): void {
|
||||
const previousSize = this.size;
|
||||
this.size = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.layout(previousSize);
|
||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.resize(this.viewItems.length - 1, this.contentSize - contentSize);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.resize(this.viewItems.length - 1, size - this.size);
|
||||
this.size = Math.max(size, this.viewItems.reduce((r, i) => r + i.size, 0));
|
||||
const previousSize = Math.max(this.size, this.contentSize);
|
||||
this.size = size;
|
||||
this.resize(this.viewItems.length - 1, size - previousSize);
|
||||
}
|
||||
|
||||
private onSashStart({ sash, start }: ISashEvent): void {
|
||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||
const sizes = this.viewItems.map(i => i.size);
|
||||
|
||||
this.sashDragState = { start, index, sizes };
|
||||
const upIndexes = range(index, -1);
|
||||
const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||
|
||||
const minDelta = -Math.min(collapseUp, expandDown);
|
||||
const maxDelta = Math.min(collapseDown, expandUp);
|
||||
|
||||
this.sashDragState = { start, index, sizes, minDelta, maxDelta };
|
||||
}
|
||||
|
||||
private onSashChange({ sash, current }: ISashEvent): void {
|
||||
const { index, start, sizes } = this.sashDragState;
|
||||
const { index, start, sizes, minDelta, maxDelta } = this.sashDragState;
|
||||
const delta = clamp(current - start, minDelta, maxDelta);
|
||||
|
||||
this.resize(index, current - start, sizes);
|
||||
this.viewItems.forEach(viewItem => viewItem.explicitSize = viewItem.size);
|
||||
this.resize(index, delta, sizes);
|
||||
}
|
||||
|
||||
private onViewChange(item: IViewItem): void {
|
||||
const index = this.viewItems.indexOf(item);
|
||||
|
||||
if (index < 0 || index >= this.viewItems.length - 1) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size = clamp(item.size, item.view.minimumSize, item.view.maximumSize);
|
||||
this.resize(index, size - item.size);
|
||||
item.size = size;
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
resizeView(index: number, size: number): void {
|
||||
if (index < 0 || index >= this.viewItems.length - 1) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.viewItems[index];
|
||||
size = Math.round(size);
|
||||
this.resize(index, size - this.viewItems[index].size);
|
||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||
let delta = size - item.size;
|
||||
|
||||
if (delta !== 0 && index < this.viewItems.length - 1) {
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
||||
|
||||
this.resize(index, deltaDown);
|
||||
delta -= deltaDown;
|
||||
}
|
||||
|
||||
if (delta !== 0 && index > 0) {
|
||||
const upIndexes = range(index - 1, -1);
|
||||
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
||||
|
||||
this.resize(index - 1, deltaUp);
|
||||
}
|
||||
}
|
||||
|
||||
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size)): void {
|
||||
@@ -228,17 +257,10 @@ export class SplitView implements IDisposable {
|
||||
const upIndexes = range(index, -1);
|
||||
const up = upIndexes.map(i => this.viewItems[i]);
|
||||
const upSizes = upIndexes.map(i => sizes[i]);
|
||||
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const down = downIndexes.map(i => this.viewItems[i]);
|
||||
const downSizes = downIndexes.map(i => sizes[i]);
|
||||
|
||||
delta = clamp(
|
||||
delta,
|
||||
-upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0),
|
||||
downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0)
|
||||
);
|
||||
|
||||
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < up.length; i++) {
|
||||
const item = up[i];
|
||||
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
||||
@@ -258,6 +280,20 @@ export class SplitView implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
let emptyDelta = this.size - contentSize;
|
||||
|
||||
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
|
||||
const item = this.viewItems[i];
|
||||
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - item.size;
|
||||
|
||||
emptyDelta -= viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ suite('Splitview', () => {
|
||||
splitview.dispose();
|
||||
});
|
||||
|
||||
test('has views as sashes as children', () => {
|
||||
test('has views and sashes as children', () => {
|
||||
const view1 = new TestView(20, 20);
|
||||
const view2 = new TestView(20, 20);
|
||||
const view3 = new TestView(20, 20);
|
||||
@@ -92,34 +92,34 @@ suite('Splitview', () => {
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
|
||||
|
||||
let sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
let sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 2, 'split view should have 2 sashes');
|
||||
|
||||
splitview.removeView(2);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 1, 'split view should have 1 sash');
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
|
||||
|
||||
splitview.removeView(0);
|
||||
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
|
||||
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-view');
|
||||
assert.equal(viewQuery.length, 0, 'split view should have no views');
|
||||
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
|
||||
sashQuery = container.querySelectorAll('.monaco-split-view2 > .monaco-sash');
|
||||
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
|
||||
|
||||
splitview.dispose();
|
||||
@@ -177,31 +177,6 @@ suite('Splitview', () => {
|
||||
view.dispose();
|
||||
});
|
||||
|
||||
test('respects preferred sizes with structural changes', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const splitview = new SplitView(container);
|
||||
splitview.layout(200);
|
||||
|
||||
splitview.addView(view1, 20);
|
||||
assert.equal(view1.size, 200, 'view1 is stretched');
|
||||
|
||||
splitview.addView(view2, 20);
|
||||
assert.equal(view1.size, 20, 'view1 size is restored');
|
||||
assert.equal(view2.size, 200 - 20, 'view2 is stretched');
|
||||
|
||||
splitview.addView(view3, 20);
|
||||
assert.equal(view1.size, 20, 'view1 size is restored');
|
||||
assert.equal(view2.size, 20, 'view2 size is restored');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
view2.dispose();
|
||||
view1.dispose();
|
||||
});
|
||||
|
||||
test('can resize views', () => {
|
||||
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
|
||||
@@ -213,27 +188,27 @@ suite('Splitview', () => {
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 size is the default');
|
||||
assert.equal(view2.size, 20, 'view2 size the the default');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
assert.equal(view1.size, 160, 'view1 is stretched');
|
||||
assert.equal(view2.size, 20, 'view2 size is 20');
|
||||
assert.equal(view3.size, 20, 'view3 size is 20');
|
||||
|
||||
splitview.resizeView(1, 40);
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 is untouched');
|
||||
assert.equal(view1.size, 140, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 40, 'view2 is stretched');
|
||||
assert.equal(view3.size, 140, 'view3 is collapsed');
|
||||
assert.equal(view3.size, 20, 'view3 stays the same');
|
||||
|
||||
splitview.resizeView(0, 70);
|
||||
|
||||
assert.equal(view1.size, 70, 'view1 is stretched');
|
||||
assert.equal(view2.size, 20, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 110, 'view3 is collapsed');
|
||||
assert.equal(view1.size, 70, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 110, 'view2 is expanded');
|
||||
assert.equal(view3.size, 20, 'view3 stays the same');
|
||||
|
||||
assert.throws(() => splitview.resizeView(2, 20));
|
||||
splitview.resizeView(2, 40);
|
||||
|
||||
assert.equal(view1.size, 70, 'view1 stays the same');
|
||||
assert.equal(view2.size, 20, 'view2 stays the same');
|
||||
assert.equal(view3.size, 110, 'view3 stays the same');
|
||||
assert.equal(view2.size, 90, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 40, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
view3.dispose();
|
||||
@@ -252,32 +227,33 @@ suite('Splitview', () => {
|
||||
splitview.addView(view2, 20);
|
||||
splitview.addView(view3, 20);
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 size is restored');
|
||||
assert.equal(view2.size, 20, 'view2 size is restored');
|
||||
assert.equal(view1.size, 160, 'view1 is stretched');
|
||||
assert.equal(view2.size, 20, 'view2 size is 20');
|
||||
assert.equal(view3.size, 20, 'view3 size is 20');
|
||||
|
||||
view1.maximumSize = 20;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 20, 'view2 stays the same');
|
||||
assert.equal(view3.size, 160, 'view3 is stretched');
|
||||
|
||||
view3.maximumSize = 20;
|
||||
view3.maximumSize = 40;
|
||||
|
||||
assert.equal(view1.size, 20, 'view1 stays the same');
|
||||
assert.equal(view2.size, 160, 'view2 is stretched');
|
||||
assert.equal(view3.size, 20, 'view3 is collapsed');
|
||||
assert.equal(view2.size, 140, 'view2 is stretched');
|
||||
assert.equal(view3.size, 40, 'view3 is collapsed');
|
||||
|
||||
view2.maximumSize = 40;
|
||||
view2.maximumSize = 200;
|
||||
|
||||
assert.equal(view1.size, 140, 'view1 is stretched');
|
||||
assert.equal(view2.size, 40, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 20, 'view3 is collapsed');
|
||||
|
||||
view3.maximumSize = 200;
|
||||
|
||||
assert.equal(view1.size, 140, 'view1 stays the same');
|
||||
assert.equal(view2.size, 40, 'view2 stays the same');
|
||||
assert.equal(view3.size, 20, 'view3 stays the same');
|
||||
assert.equal(view1.size, 20, 'view1 stays the same');
|
||||
assert.equal(view2.size, 140, 'view2 stays the same');
|
||||
assert.equal(view3.size, 40, 'view3 stays the same');
|
||||
|
||||
view3.maximumSize = Number.POSITIVE_INFINITY;
|
||||
view3.minimumSize = 100;
|
||||
|
||||
assert.equal(view1.size, 80, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 20, 'view2 stays the same');
|
||||
assert.equal(view1.size, 20, 'view1 is collapsed');
|
||||
assert.equal(view2.size, 80, 'view2 is collapsed');
|
||||
assert.equal(view3.size, 100, 'view3 is stretched');
|
||||
|
||||
splitview.dispose();
|
||||
|
||||
Reference in New Issue
Block a user