|
|
|
|
@@ -4,7 +4,7 @@
|
|
|
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
import 'vs/css!./splitview';
|
|
|
|
|
import { IDisposable, combinedDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
|
|
|
|
import { IDisposable, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
|
|
|
|
import { Event, Emitter } from 'vs/base/common/event';
|
|
|
|
|
import * as types from 'vs/base/common/types';
|
|
|
|
|
import * as dom from 'vs/base/browser/dom';
|
|
|
|
|
@@ -47,7 +47,9 @@ export interface IView {
|
|
|
|
|
readonly maximumSize: number;
|
|
|
|
|
readonly onDidChange: Event<number | undefined>;
|
|
|
|
|
readonly priority?: LayoutPriority;
|
|
|
|
|
readonly snap?: boolean;
|
|
|
|
|
layout(size: number, orientation: Orientation): void;
|
|
|
|
|
setVisible?(visible: boolean): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ISashEvent {
|
|
|
|
|
@@ -57,12 +59,81 @@ interface ISashEvent {
|
|
|
|
|
alt: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IViewItem {
|
|
|
|
|
view: IView;
|
|
|
|
|
size: number;
|
|
|
|
|
container: HTMLElement;
|
|
|
|
|
disposable: IDisposable;
|
|
|
|
|
layout(): void;
|
|
|
|
|
abstract class ViewItem {
|
|
|
|
|
|
|
|
|
|
set size(size: number) {
|
|
|
|
|
this._size = size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get size(): number {
|
|
|
|
|
return this._size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private cachedSize: number | undefined = undefined;
|
|
|
|
|
|
|
|
|
|
get visible(): boolean {
|
|
|
|
|
return typeof this.cachedSize === 'undefined';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
set visible(visible: boolean) {
|
|
|
|
|
if (visible === this.visible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (visible) {
|
|
|
|
|
this.size = this.cachedSize!;
|
|
|
|
|
this.cachedSize = undefined;
|
|
|
|
|
} else {
|
|
|
|
|
this.cachedSize = this.size;
|
|
|
|
|
this.size = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dom.toggleClass(this.container, 'visible', visible);
|
|
|
|
|
|
|
|
|
|
if (this.view.setVisible) {
|
|
|
|
|
this.view.setVisible(visible);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; }
|
|
|
|
|
get viewMinimumSize(): number { return this.view.minimumSize; }
|
|
|
|
|
|
|
|
|
|
get maximumSize(): number { return this.visible ? this.view.maximumSize : 0; }
|
|
|
|
|
get viewMaximumSize(): number { return this.view.maximumSize; }
|
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
abstract layout(): void;
|
|
|
|
|
|
|
|
|
|
layoutView(orientation: Orientation): void {
|
|
|
|
|
this.view.layout(this.size, orientation);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dispose(): IView {
|
|
|
|
|
this.disposable.dispose();
|
|
|
|
|
return this.view;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class VerticalViewItem extends ViewItem {
|
|
|
|
|
|
|
|
|
|
layout(): void {
|
|
|
|
|
this.container.style.height = `${this.size}px`;
|
|
|
|
|
this.layoutView(Orientation.VERTICAL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class HorizontalViewItem extends ViewItem {
|
|
|
|
|
|
|
|
|
|
layout(): void {
|
|
|
|
|
this.container.style.width = `${this.size}px`;
|
|
|
|
|
this.layoutView(Orientation.HORIZONTAL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ISashItem {
|
|
|
|
|
@@ -70,6 +141,11 @@ interface ISashItem {
|
|
|
|
|
disposable: IDisposable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ISashDragSnapState {
|
|
|
|
|
readonly index: number;
|
|
|
|
|
readonly limitDelta: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ISashDragState {
|
|
|
|
|
index: number;
|
|
|
|
|
start: number;
|
|
|
|
|
@@ -78,6 +154,8 @@ interface ISashDragState {
|
|
|
|
|
minDelta: number;
|
|
|
|
|
maxDelta: number;
|
|
|
|
|
alt: boolean;
|
|
|
|
|
snapBefore: ISashDragSnapState | undefined;
|
|
|
|
|
snapAfter: ISashDragSnapState | undefined;
|
|
|
|
|
disposable: IDisposable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -104,7 +182,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
private size = 0;
|
|
|
|
|
private contentSize = 0;
|
|
|
|
|
private proportions: undefined | number[] = undefined;
|
|
|
|
|
private viewItems: IViewItem[] = [];
|
|
|
|
|
private viewItems: ViewItem[] = [];
|
|
|
|
|
private sashItems: ISashItem[] = [];
|
|
|
|
|
private sashDragState: ISashDragState;
|
|
|
|
|
private state: State = State.Idle;
|
|
|
|
|
@@ -122,11 +200,11 @@ export class SplitView extends Disposable {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get minimumSize(): number {
|
|
|
|
|
return this.viewItems.reduce((r, item) => r + item.view.minimumSize, 0);
|
|
|
|
|
return this.viewItems.reduce((r, item) => r + item.minimumSize, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get maximumSize(): number {
|
|
|
|
|
return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.view.maximumSize, 0);
|
|
|
|
|
return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.maximumSize, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _orthogonalStartSash: Sash | undefined;
|
|
|
|
|
@@ -199,16 +277,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
|
|
|
|
const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container));
|
|
|
|
|
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
|
|
|
|
|
|
|
|
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
|
|
|
|
? () => item.container.style.height = `${item.size}px`
|
|
|
|
|
: () => item.container.style.width = `${item.size}px`;
|
|
|
|
|
|
|
|
|
|
const layout = () => {
|
|
|
|
|
layoutContainer();
|
|
|
|
|
item.view.layout(item.size, this.orientation);
|
|
|
|
|
};
|
|
|
|
|
const disposable = combinedDisposable(onChangeDisposable, containerDisposable);
|
|
|
|
|
|
|
|
|
|
let viewSize: number;
|
|
|
|
|
|
|
|
|
|
@@ -220,7 +289,10 @@ export class SplitView extends Disposable {
|
|
|
|
|
viewSize = view.minimumSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const item: IViewItem = { view, container, size: viewSize, layout, disposable };
|
|
|
|
|
const item = this.orientation === Orientation.VERTICAL
|
|
|
|
|
? new VerticalViewItem(container, view, viewSize, disposable)
|
|
|
|
|
: new HorizontalViewItem(container, view, viewSize, disposable);
|
|
|
|
|
|
|
|
|
|
this.viewItems.splice(index, 0, item);
|
|
|
|
|
|
|
|
|
|
// Add sash
|
|
|
|
|
@@ -245,7 +317,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
const onEndDisposable = onEnd(this.onSashEnd, this);
|
|
|
|
|
const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));
|
|
|
|
|
|
|
|
|
|
const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
|
|
|
|
const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash);
|
|
|
|
|
const sashItem: ISashItem = { sash, disposable };
|
|
|
|
|
|
|
|
|
|
this.sashItems.splice(index - 1, 0, sashItem);
|
|
|
|
|
@@ -280,7 +352,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
// Remove view
|
|
|
|
|
const viewItem = this.viewItems.splice(index, 1)[0];
|
|
|
|
|
viewItem.disposable.dispose();
|
|
|
|
|
const view = viewItem.dispose();
|
|
|
|
|
|
|
|
|
|
// Remove sash
|
|
|
|
|
if (this.viewItems.length >= 1) {
|
|
|
|
|
@@ -296,7 +368,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
this.distributeViewSizes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return viewItem.view;
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
moveView(from: number, to: number): void {
|
|
|
|
|
@@ -327,6 +399,27 @@ export class SplitView extends Disposable {
|
|
|
|
|
this.addView(fromView, toSize, to);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isViewVisible(index: number): boolean {
|
|
|
|
|
if (index < 0 || index >= this.viewItems.length) {
|
|
|
|
|
throw new Error('Index out of bounds');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const viewItem = this.viewItems[index];
|
|
|
|
|
return viewItem.visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setViewVisible(index: number, visible: boolean): void {
|
|
|
|
|
if (index < 0 || index >= this.viewItems.length) {
|
|
|
|
|
throw new Error('Index out of bounds');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const viewItem = this.viewItems[index];
|
|
|
|
|
viewItem.visible = visible;
|
|
|
|
|
|
|
|
|
|
this.distributeEmptySpace(index);
|
|
|
|
|
this.layoutViews();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void {
|
|
|
|
|
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
|
|
|
|
const lowPriorityIndexes = typeof lowPriorityIndex === 'number' ? [lowPriorityIndex] : undefined;
|
|
|
|
|
@@ -344,14 +437,14 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
if (!this.proportions) {
|
|
|
|
|
const indexes = range(this.viewItems.length);
|
|
|
|
|
const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].view.priority === LayoutPriority.Low);
|
|
|
|
|
const highPriorityIndexes = indexes.filter(i => this.viewItems[i].view.priority === LayoutPriority.High);
|
|
|
|
|
const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low);
|
|
|
|
|
const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High);
|
|
|
|
|
|
|
|
|
|
this.resize(this.viewItems.length - 1, size - previousSize, undefined, lowPriorityIndexes, highPriorityIndexes);
|
|
|
|
|
} else {
|
|
|
|
|
for (let i = 0; i < this.viewItems.length; i++) {
|
|
|
|
|
const item = this.viewItems[i];
|
|
|
|
|
item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize);
|
|
|
|
|
item.size = clamp(Math.round(this.proportions[i] * size), item.minimumSize, item.maximumSize);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -369,10 +462,10 @@ export class SplitView extends Disposable {
|
|
|
|
|
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
|
|
|
|
|
|
|
|
|
// This way, we can press Alt while we resize a sash, macOS style!
|
|
|
|
|
const disposable = combinedDisposable([
|
|
|
|
|
const disposable = combinedDisposable(
|
|
|
|
|
domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)),
|
|
|
|
|
domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false))
|
|
|
|
|
]);
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const resetSashDragState = (start: number, alt: boolean) => {
|
|
|
|
|
const sizes = this.viewItems.map(i => i.size);
|
|
|
|
|
@@ -391,35 +484,71 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
if (isLastSash) {
|
|
|
|
|
const viewItem = this.viewItems[index];
|
|
|
|
|
minDelta = (viewItem.view.minimumSize - viewItem.size) / 2;
|
|
|
|
|
maxDelta = (viewItem.view.maximumSize - viewItem.size) / 2;
|
|
|
|
|
minDelta = (viewItem.minimumSize - viewItem.size) / 2;
|
|
|
|
|
maxDelta = (viewItem.maximumSize - viewItem.size) / 2;
|
|
|
|
|
} else {
|
|
|
|
|
const viewItem = this.viewItems[index + 1];
|
|
|
|
|
minDelta = (viewItem.size - viewItem.view.maximumSize) / 2;
|
|
|
|
|
maxDelta = (viewItem.size - viewItem.view.minimumSize) / 2;
|
|
|
|
|
minDelta = (viewItem.size - viewItem.maximumSize) / 2;
|
|
|
|
|
maxDelta = (viewItem.size - viewItem.minimumSize) / 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, disposable };
|
|
|
|
|
let snapBefore: ISashDragSnapState | undefined;
|
|
|
|
|
let snapAfter: ISashDragSnapState | 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].viewMaximumSize - 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].viewMaximumSize), 0);
|
|
|
|
|
const minDelta = Math.max(minDeltaUp, minDeltaDown);
|
|
|
|
|
const maxDelta = Math.min(maxDeltaDown, maxDeltaUp);
|
|
|
|
|
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
|
|
|
|
|
const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
|
|
|
|
|
|
|
|
|
|
if (typeof snapBeforeIndex === 'number') {
|
|
|
|
|
const viewItem = this.viewItems[snapBeforeIndex];
|
|
|
|
|
const halfSize = Math.floor(viewItem.viewMinimumSize / 2);
|
|
|
|
|
|
|
|
|
|
snapBefore = {
|
|
|
|
|
index: snapBeforeIndex,
|
|
|
|
|
limitDelta: viewItem.visible ? minDelta - halfSize : minDelta + halfSize
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof snapAfterIndex === 'number') {
|
|
|
|
|
const viewItem = this.viewItems[snapAfterIndex];
|
|
|
|
|
const halfSize = Math.floor(viewItem.viewMinimumSize / 2);
|
|
|
|
|
|
|
|
|
|
snapAfter = {
|
|
|
|
|
index: snapAfterIndex,
|
|
|
|
|
limitDelta: viewItem.visible ? maxDelta + halfSize : maxDelta - halfSize
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, snapBefore, snapAfter, 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, snapBefore, snapAfter } = 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, snapBefore, snapAfter);
|
|
|
|
|
|
|
|
|
|
if (alt) {
|
|
|
|
|
const isLastSash = index === this.sashItems.length - 1;
|
|
|
|
|
const newSizes = this.viewItems.map(i => i.size);
|
|
|
|
|
const viewItemIndex = isLastSash ? index : index + 1;
|
|
|
|
|
const viewItem = this.viewItems[viewItemIndex];
|
|
|
|
|
const newMinDelta = viewItem.size - viewItem.view.maximumSize;
|
|
|
|
|
const newMaxDelta = viewItem.size - viewItem.view.minimumSize;
|
|
|
|
|
const newMinDelta = viewItem.size - viewItem.maximumSize;
|
|
|
|
|
const newMaxDelta = viewItem.size - viewItem.minimumSize;
|
|
|
|
|
const resizeIndex = isLastSash ? index - 1 : index + 1;
|
|
|
|
|
|
|
|
|
|
this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta);
|
|
|
|
|
@@ -435,7 +564,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
this.saveProportions();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onViewChange(item: IViewItem, size: number | undefined): void {
|
|
|
|
|
private onViewChange(item: ViewItem, size: number | undefined): void {
|
|
|
|
|
const index = this.viewItems.indexOf(item);
|
|
|
|
|
|
|
|
|
|
if (index < 0 || index >= this.viewItems.length) {
|
|
|
|
|
@@ -443,7 +572,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size = typeof size === 'number' ? size : item.size;
|
|
|
|
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
|
|
|
|
size = clamp(size, item.minimumSize, item.maximumSize);
|
|
|
|
|
|
|
|
|
|
if (this.inverseAltBehavior && index > 0) {
|
|
|
|
|
// In this case, we want the view to grow or shrink both sides equally
|
|
|
|
|
@@ -470,13 +599,13 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
const item = this.viewItems[index];
|
|
|
|
|
size = Math.round(size);
|
|
|
|
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
|
|
|
|
size = clamp(size, item.minimumSize, item.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 collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].minimumSize), 0);
|
|
|
|
|
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].maximumSize - this.viewItems[i].size), 0);
|
|
|
|
|
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
|
|
|
|
|
|
|
|
|
this.resize(index, deltaDown);
|
|
|
|
|
@@ -485,8 +614,8 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
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 collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].minimumSize), 0);
|
|
|
|
|
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].maximumSize - this.viewItems[i].size), 0);
|
|
|
|
|
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
|
|
|
|
|
|
|
|
|
this.resize(index - 1, deltaUp);
|
|
|
|
|
@@ -521,7 +650,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,
|
|
|
|
|
snapBefore?: ISashDragSnapState,
|
|
|
|
|
snapAfter?: ISashDragSnapState
|
|
|
|
|
): number {
|
|
|
|
|
if (index < 0 || index >= this.viewItems.length) {
|
|
|
|
|
return 0;
|
|
|
|
|
@@ -550,18 +681,38 @@ export class SplitView extends Disposable {
|
|
|
|
|
const downItems = downIndexes.map(i => this.viewItems[i]);
|
|
|
|
|
const downSizes = downIndexes.map(i => sizes[i]);
|
|
|
|
|
|
|
|
|
|
const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.minimumSize - sizes[i]), 0);
|
|
|
|
|
const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
|
|
|
|
const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
|
|
|
|
const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.maximumSize), 0);
|
|
|
|
|
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, overloadMinDelta);
|
|
|
|
|
const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta);
|
|
|
|
|
|
|
|
|
|
let snapped = false;
|
|
|
|
|
|
|
|
|
|
if (snapBefore) {
|
|
|
|
|
const snapView = this.viewItems[snapBefore.index];
|
|
|
|
|
const visible = delta >= snapBefore.limitDelta;
|
|
|
|
|
snapped = visible !== snapView.visible;
|
|
|
|
|
snapView.visible = visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!snapped && snapAfter) {
|
|
|
|
|
const snapView = this.viewItems[snapAfter.index];
|
|
|
|
|
const visible = delta < snapAfter.limitDelta;
|
|
|
|
|
snapped = visible !== snapView.visible;
|
|
|
|
|
snapView.visible = visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (snapped) {
|
|
|
|
|
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++) {
|
|
|
|
|
const item = upItems[i];
|
|
|
|
|
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
|
|
|
|
const size = clamp(upSizes[i] + deltaUp, item.minimumSize, item.maximumSize);
|
|
|
|
|
const viewDelta = size - upSizes[i];
|
|
|
|
|
|
|
|
|
|
deltaUp -= viewDelta;
|
|
|
|
|
@@ -570,7 +721,7 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
for (let i = 0, deltaDown = delta; i < downItems.length; i++) {
|
|
|
|
|
const item = downItems[i];
|
|
|
|
|
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
|
|
|
|
const size = clamp(downSizes[i] - deltaDown, item.minimumSize, item.maximumSize);
|
|
|
|
|
const viewDelta = size - downSizes[i];
|
|
|
|
|
|
|
|
|
|
deltaDown += viewDelta;
|
|
|
|
|
@@ -580,13 +731,19 @@ export class SplitView extends Disposable {
|
|
|
|
|
return delta;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private distributeEmptySpace(): void {
|
|
|
|
|
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
|
|
|
|
private distributeEmptySpace(lowPriorityIndex?: number): void {
|
|
|
|
|
const 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 indexes = range(this.viewItems.length - 1, -1);
|
|
|
|
|
|
|
|
|
|
if (typeof lowPriorityIndex === 'number') {
|
|
|
|
|
pushToEnd(indexes, lowPriorityIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) {
|
|
|
|
|
const item = this.viewItems[indexes[i]];
|
|
|
|
|
const size = clamp(item.size + emptyDelta, item.minimumSize, item.maximumSize);
|
|
|
|
|
const viewDelta = size - item.size;
|
|
|
|
|
|
|
|
|
|
emptyDelta -= viewDelta;
|
|
|
|
|
@@ -606,31 +763,43 @@ export class SplitView extends Disposable {
|
|
|
|
|
|
|
|
|
|
// Update sashes enablement
|
|
|
|
|
let previous = false;
|
|
|
|
|
const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
|
|
|
|
|
const collapsesDown = this.viewItems.map(i => previous = (i.size - i.minimumSize > 0) || previous);
|
|
|
|
|
|
|
|
|
|
previous = false;
|
|
|
|
|
const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
|
|
|
|
|
const expandsDown = this.viewItems.map(i => previous = (i.maximumSize - i.size > 0) || previous);
|
|
|
|
|
|
|
|
|
|
const reverseViews = [...this.viewItems].reverse();
|
|
|
|
|
previous = false;
|
|
|
|
|
const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
|
|
|
|
|
const collapsesUp = reverseViews.map(i => previous = (i.size - i.minimumSize > 0) || previous).reverse();
|
|
|
|
|
|
|
|
|
|
previous = false;
|
|
|
|
|
const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
|
|
|
|
|
const expandsUp = reverseViews.map(i => previous = (i.maximumSize - i.size > 0) || previous).reverse();
|
|
|
|
|
|
|
|
|
|
this.sashItems.forEach((s, i) => {
|
|
|
|
|
const min = !(collapsesDown[i] && expandsUp[i + 1]);
|
|
|
|
|
const max = !(expandsDown[i] && collapsesUp[i + 1]);
|
|
|
|
|
this.sashItems.forEach(({ sash }, index) => {
|
|
|
|
|
const min = !(collapsesDown[index] && expandsUp[index + 1]);
|
|
|
|
|
const max = !(expandsDown[index] && collapsesUp[index + 1]);
|
|
|
|
|
|
|
|
|
|
if (min && max) {
|
|
|
|
|
s.sash.state = SashState.Disabled;
|
|
|
|
|
const upIndexes = range(index, -1);
|
|
|
|
|
const downIndexes = range(index + 1, this.viewItems.length);
|
|
|
|
|
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
|
|
|
|
|
const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
|
|
|
|
|
|
|
|
|
|
if (typeof snapBeforeIndex === 'number' && !this.viewItems[snapBeforeIndex].visible) {
|
|
|
|
|
sash.state = SashState.Minimum;
|
|
|
|
|
} else if (typeof snapAfterIndex === 'number' && !this.viewItems[snapAfterIndex].visible) {
|
|
|
|
|
sash.state = SashState.Maximum;
|
|
|
|
|
} else {
|
|
|
|
|
sash.state = SashState.Disabled;
|
|
|
|
|
}
|
|
|
|
|
} else if (min && !max) {
|
|
|
|
|
s.sash.state = SashState.Minimum;
|
|
|
|
|
sash.state = SashState.Minimum;
|
|
|
|
|
} else if (!min && max) {
|
|
|
|
|
s.sash.state = SashState.Maximum;
|
|
|
|
|
sash.state = SashState.Maximum;
|
|
|
|
|
} else {
|
|
|
|
|
s.sash.state = SashState.Enabled;
|
|
|
|
|
sash.state = SashState.Enabled;
|
|
|
|
|
}
|
|
|
|
|
// }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -648,10 +817,32 @@ export class SplitView extends Disposable {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private findFirstSnapIndex(indexes: number[]): number | undefined {
|
|
|
|
|
// visible views first
|
|
|
|
|
for (const index of indexes) {
|
|
|
|
|
if (!this.viewItems[index].visible) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.viewItems[index].snap) {
|
|
|
|
|
return index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// then, hidden views
|
|
|
|
|
for (const index of indexes) {
|
|
|
|
|
if (!this.viewItems[index].visible && this.viewItems[index].snap) {
|
|
|
|
|
return index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dispose(): void {
|
|
|
|
|
super.dispose();
|
|
|
|
|
|
|
|
|
|
this.viewItems.forEach(i => i.disposable.dispose());
|
|
|
|
|
this.viewItems.forEach(i => i.dispose());
|
|
|
|
|
this.viewItems = [];
|
|
|
|
|
|
|
|
|
|
this.sashItems.forEach(i => i.disposable.dispose());
|
|
|
|
|
|