diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 412c7856cec..90cb255f1c6 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -47,6 +47,7 @@ export interface IView { readonly maximumSize: number; readonly onDidChange: Event; readonly priority?: LayoutPriority; + readonly snap?: boolean; layout(size: number, orientation: Orientation): void; setVisible?(visible: boolean): void; } @@ -97,6 +98,7 @@ abstract class ViewItem { get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; } get maximumSize(): number { return this.visible ? this.view.maximumSize : 0; } get priority(): LayoutPriority | undefined { return this.view.priority; } + get snap(): boolean { return !!this.view.snap; } constructor(protected container: HTMLElement, private view: IView, private _size: number, private disposable: IDisposable) { dom.addClass(container, 'visible'); @@ -143,6 +145,8 @@ interface ISashDragState { minDelta: number; maxDelta: number; alt: boolean; + snapIndex: number | undefined; + snapLimitDelta: number | undefined; disposable: IDisposable; } @@ -480,18 +484,40 @@ export class SplitView extends Disposable { } } - this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, disposable }; + let snapIndex: number | undefined; + let snapLimitDelta: number | undefined; + + if (!alt) { + const upIndexes = range(index, -1); + const downIndexes = range(index + 1, this.viewItems.length); + const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].minimumSize - sizes[i]), 0); + const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].maximumSize - sizes[i]), 0); + const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].minimumSize), 0); + const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].maximumSize), 0); + const minDelta = Math.max(minDeltaUp, minDeltaDown); + const maxDelta = Math.min(maxDeltaDown, maxDeltaUp); + + if (this.viewItems[index].snap) { + snapIndex = index; + snapLimitDelta = minDelta - (this.viewItems[index].minimumSize / 2); + } else if (this.viewItems[index + 1].snap) { + snapIndex = index + 1; + snapLimitDelta = maxDelta + (this.viewItems[index + 1].minimumSize / 2); + } + } + + this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, snapIndex, snapLimitDelta, disposable }; }; resetSashDragState(start, alt); } private onSashChange({ current }: ISashEvent): void { - const { index, start, sizes, alt, minDelta, maxDelta } = this.sashDragState; + const { index, start, sizes, alt, minDelta, maxDelta, snapIndex, snapLimitDelta } = this.sashDragState; this.sashDragState.current = current; const delta = current - start; - const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta); + const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta, snapIndex, snapLimitDelta); if (alt) { const isLastSash = index === this.sashItems.length - 1; @@ -601,7 +627,9 @@ export class SplitView extends Disposable { lowPriorityIndexes?: number[], highPriorityIndexes?: number[], overloadMinDelta: number = Number.NEGATIVE_INFINITY, - overloadMaxDelta: number = Number.POSITIVE_INFINITY + overloadMaxDelta: number = Number.POSITIVE_INFINITY, + snapIndex?: number, + snapLimitDelta?: number ): number { if (index < 0 || index >= this.viewItems.length) { return 0; @@ -637,6 +665,22 @@ export class SplitView extends Disposable { const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta); const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta); + if (typeof snapIndex === 'number' && typeof snapLimitDelta === 'number') { + const snapView = this.viewItems[snapIndex]; + + if (snapIndex === index) { // up + if (delta >= snapLimitDelta) { + snapView.visible = true; + } else { + snapView.visible = false; + } + } else { // down + snapView.visible = delta < snapLimitDelta; + } + + return this.resize(index, delta, sizes, lowPriorityIndexes, highPriorityIndexes, overloadMinDelta, overloadMaxDelta); + } + delta = clamp(delta, minDelta, maxDelta); for (let i = 0, deltaUp = delta; i < upItems.length; i++) { diff --git a/test/splitview/public/index.html b/test/splitview/public/index.html index f20c7961519..dfb7e88bd4b 100644 --- a/test/splitview/public/index.html +++ b/test/splitview/public/index.html @@ -36,7 +36,7 @@ class View { static ID = 0; - constructor() { + constructor(snap) { this.element = document.createElement('div'); this.element.className = 'view'; this.element.style.backgroundColor = `hsl(${rand(1, 360)}, 50%, 70%)`; @@ -44,7 +44,7 @@ this.minimumSize = 100; this.maximumSize = Number.POSITIVE_INFINITY; this.onDidChange = Event.None; - this.snap = true; + this.snap = snap; } layout(size, orientation) { @@ -56,9 +56,9 @@ const splitview = new SplitView(container, {}); splitview.layout(600); - const view1 = new View(); - const view2 = new View(); - const view3 = new View(); + const view1 = new View(true); + const view2 = new View(false); + const view3 = new View(true); splitview.addView(view1, Sizing.Distribute); splitview.addView(view2, Sizing.Distribute);