splitview.ts 26.1 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./splitview';
7
import { IDisposable, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
8
import { Event, Emitter } from 'vs/base/common/event';
9 10
import * as types from 'vs/base/common/types';
import * as dom from 'vs/base/browser/dom';
J
Joao Moreno 已提交
11
import { clamp } from 'vs/base/common/numbers';
J
Joao Moreno 已提交
12
import { range, firstIndex, pushToStart, pushToEnd } from 'vs/base/common/arrays';
J
Joao Moreno 已提交
13
import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash';
14
import { Color } from 'vs/base/common/color';
J
Joao Moreno 已提交
15
import { domEvent } from 'vs/base/browser/event';
J
Joao Moreno 已提交
16
export { Orientation } from 'vs/base/browser/ui/sash/sash';
E
Erich Gamma 已提交
17

18 19 20 21 22 23 24 25
export interface ISplitViewStyles {
	separatorBorder: Color;
}

const defaultStyles: ISplitViewStyles = {
	separatorBorder: Color.transparent
};

J
Joao Moreno 已提交
26
export interface ISplitViewOptions {
E
Erich Gamma 已提交
27
	orientation?: Orientation; // default Orientation.VERTICAL
28
	styles?: ISplitViewStyles;
29 30
	orthogonalStartSash?: Sash;
	orthogonalEndSash?: Sash;
I
isidor 已提交
31
	inverseAltBehavior?: boolean;
J
Joao Moreno 已提交
32
	proportionalLayout?: boolean; // default true
J
Joao Moreno 已提交
33 34
}

J
Joao Moreno 已提交
35 36 37 38 39 40 41 42 43
/**
 * Only used when `proportionalLayout` is false.
 */
export const enum LayoutPriority {
	Normal,
	Low,
	High
}

J
Joao Moreno 已提交
44
export interface IView {
J
Joao Moreno 已提交
45
	readonly element: HTMLElement;
J
Joao Moreno 已提交
46 47 48
	readonly minimumSize: number;
	readonly maximumSize: number;
	readonly onDidChange: Event<number | undefined>;
J
Joao Moreno 已提交
49
	readonly priority?: LayoutPriority;
J
Joao Moreno 已提交
50
	readonly snap?: boolean;
J
Joao Moreno 已提交
51
	layout(size: number, orientation: Orientation): void;
J
Joao Moreno 已提交
52
	setVisible?(visible: boolean): void;
J
Joao Moreno 已提交
53 54
}

J
Joao Moreno 已提交
55 56 57 58
interface ISashEvent {
	sash: Sash;
	start: number;
	current: number;
J
Joao Moreno 已提交
59
	alt: boolean;
E
Erich Gamma 已提交
60 61
}

J
Joao Moreno 已提交
62 63 64 65 66 67 68 69 70 71
abstract class ViewItem {

	set size(size: number) {
		this._size = size;
	}

	get size(): number {
		return this._size;
	}

J
Joao Moreno 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
	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);
J
Joao Moreno 已提交
92 93 94 95

		if (this.view.setVisible) {
			this.view.setVisible(visible);
		}
J
Joao Moreno 已提交
96 97 98
	}

	get minimumSize(): number { return this.visible ? this.view.minimumSize : 0; }
J
Joao Moreno 已提交
99
	get viewMinimumSize(): number { return this.view.minimumSize; }
J
wip  
Joao Moreno 已提交
100

J
Joao Moreno 已提交
101
	get maximumSize(): number { return this.visible ? this.view.maximumSize : 0; }
J
Joao Moreno 已提交
102
	get viewMaximumSize(): number { return this.view.maximumSize; }
J
wip  
Joao Moreno 已提交
103

J
Joao Moreno 已提交
104
	get priority(): LayoutPriority | undefined { return this.view.priority; }
J
Joao Moreno 已提交
105
	get snap(): boolean { return !!this.view.snap; }
J
Joao Moreno 已提交
106

J
Joao Moreno 已提交
107 108 109
	constructor(protected container: HTMLElement, private view: IView, private _size: number, private disposable: IDisposable) {
		dom.addClass(container, 'visible');
	}
J
Joao Moreno 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

	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);
	}
E
Erich Gamma 已提交
137 138
}

J
Joao Moreno 已提交
139 140 141
interface ISashItem {
	sash: Sash;
	disposable: IDisposable;
E
Erich Gamma 已提交
142 143
}

J
wip  
Joao Moreno 已提交
144 145 146 147 148
interface ISashDragSnapState {
	readonly index: number;
	readonly limitDelta: number;
}

J
Joao Moreno 已提交
149 150 151
interface ISashDragState {
	index: number;
	start: number;
J
Joao Moreno 已提交
152
	current: number;
J
Joao Moreno 已提交
153
	sizes: number[];
J
Joao Moreno 已提交
154 155 156
	minDelta: number;
	maxDelta: number;
	alt: boolean;
J
wip  
Joao Moreno 已提交
157 158
	snapBefore: ISashDragSnapState | undefined;
	snapAfter: ISashDragSnapState | undefined;
J
Joao Moreno 已提交
159
	disposable: IDisposable;
160 161
}

162 163 164 165 166
enum State {
	Idle,
	Busy
}

167 168 169 170 171 172 173
export type DistributeSizing = { type: 'distribute' };
export type SplitSizing = { type: 'split', index: number };
export type Sizing = DistributeSizing | SplitSizing;

export namespace Sizing {
	export const Distribute: DistributeSizing = { type: 'distribute' };
	export function Split(index: number): SplitSizing { return { type: 'split', index }; }
J
Joao Moreno 已提交
174 175
}

176
export class SplitView extends Disposable {
S
Sandeep Somavarapu 已提交
177

178
	readonly orientation: Orientation;
J
Joao Moreno 已提交
179
	readonly el: HTMLElement;
180
	private sashContainer: HTMLElement;
181
	private viewContainer: HTMLElement;
J
Joao Moreno 已提交
182 183
	private size = 0;
	private contentSize = 0;
184
	private proportions: undefined | number[] = undefined;
J
Joao Moreno 已提交
185
	private viewItems: ViewItem[] = [];
J
Joao Moreno 已提交
186 187
	private sashItems: ISashItem[] = [];
	private sashDragState: ISashDragState;
188
	private state: State = State.Idle;
I
isidor 已提交
189
	private inverseAltBehavior: boolean;
J
Joao Moreno 已提交
190
	private proportionalLayout: boolean;
E
Erich Gamma 已提交
191

192
	private _onDidSashChange = this._register(new Emitter<number>());
J
Joao Moreno 已提交
193
	readonly onDidSashChange = this._onDidSashChange.event;
194 195

	private _onDidSashReset = this._register(new Emitter<number>());
196
	readonly onDidSashReset = this._onDidSashReset.event;
J
Joao Moreno 已提交
197

J
Joao Moreno 已提交
198 199
	get length(): number {
		return this.viewItems.length;
S
Sandeep Somavarapu 已提交
200
	}
J
Joao Moreno 已提交
201

202
	get minimumSize(): number {
J
Joao Moreno 已提交
203
		return this.viewItems.reduce((r, item) => r + item.minimumSize, 0);
204 205 206
	}

	get maximumSize(): number {
J
Joao Moreno 已提交
207
		return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.maximumSize, 0);
208 209
	}

210 211 212
	private _orthogonalStartSash: Sash | undefined;
	get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
	set orthogonalStartSash(sash: Sash | undefined) {
213
		for (const sashItem of this.sashItems) {
214
			sashItem.sash.orthogonalStartSash = sash;
215 216
		}

217
		this._orthogonalStartSash = sash;
218 219
	}

220 221 222
	private _orthogonalEndSash: Sash | undefined;
	get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
	set orthogonalEndSash(sash: Sash | undefined) {
223
		for (const sashItem of this.sashItems) {
224
			sashItem.sash.orthogonalEndSash = sash;
225 226
		}

227
		this._orthogonalEndSash = sash;
228 229 230 231 232 233
	}

	get sashes(): Sash[] {
		return this.sashItems.map(s => s.sash);
	}

M
Matt Bierner 已提交
234
	constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
235 236
		super();

J
Joao Moreno 已提交
237
		this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
I
isidor 已提交
238
		this.inverseAltBehavior = !!options.inverseAltBehavior;
J
Joao Moreno 已提交
239
		this.proportionalLayout = types.isUndefined(options.proportionalLayout) ? true : !!options.proportionalLayout;
S
Sandeep Somavarapu 已提交
240

J
Joao Moreno 已提交
241 242 243 244
		this.el = document.createElement('div');
		dom.addClass(this.el, 'monaco-split-view2');
		dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
		container.appendChild(this.el);
245

246 247
		this.sashContainer = dom.append(this.el, dom.$('.sash-container'));
		this.viewContainer = dom.append(this.el, dom.$('.split-view-container'));
248 249 250 251 252 253 254 255 256 257 258 259

		this.style(options.styles || defaultStyles);
	}

	style(styles: ISplitViewStyles): void {
		if (styles.separatorBorder.isTransparent()) {
			dom.removeClass(this.el, 'separator-border');
			this.el.style.removeProperty('--separator-border');
		} else {
			dom.addClass(this.el, 'separator-border');
			this.el.style.setProperty('--separator-border', styles.separatorBorder.toString());
		}
E
Erich Gamma 已提交
260 261
	}

J
Joao Moreno 已提交
262
	addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
263 264 265 266 267 268
		if (this.state !== State.Idle) {
			throw new Error('Cant modify splitview');
		}

		this.state = State.Busy;

J
Joao Moreno 已提交
269 270
		// Add view
		const container = dom.$('.split-view-view');
E
Erich Gamma 已提交
271

J
Joao Moreno 已提交
272
		if (index === this.viewItems.length) {
273
			this.viewContainer.appendChild(container);
E
Erich Gamma 已提交
274
		} else {
275
			this.viewContainer.insertBefore(container, this.viewContainer.children.item(index));
E
Erich Gamma 已提交
276 277
		}

J
Joao Moreno 已提交
278
		const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
279
		const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container));
280
		const disposable = combinedDisposable(onChangeDisposable, containerDisposable);
E
Erich Gamma 已提交
281

282 283 284 285 286 287 288 289 290 291
		let viewSize: number;

		if (typeof size === 'number') {
			viewSize = size;
		} else if (size.type === 'split') {
			viewSize = this.getViewSize(size.index) / 2;
		} else {
			viewSize = view.minimumSize;
		}

J
Joao Moreno 已提交
292 293 294 295
		const item = this.orientation === Orientation.VERTICAL
			? new VerticalViewItem(container, view, viewSize, disposable)
			: new HorizontalViewItem(container, view, viewSize, disposable);

J
Joao Moreno 已提交
296
		this.viewItems.splice(index, 0, item);
E
Erich Gamma 已提交
297

J
Joao Moreno 已提交
298 299 300
		// Add sash
		if (this.viewItems.length > 1) {
			const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
B
Benjamin Pasero 已提交
301
			const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) } : { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) };
302 303
			const sash = new Sash(this.sashContainer, layoutProvider, {
				orientation,
304 305
				orthogonalStartSash: this.orthogonalStartSash,
				orthogonalEndSash: this.orthogonalEndSash
306
			});
J
Joao Moreno 已提交
307

J
Joao Moreno 已提交
308
			const sashEventMapper = this.orientation === Orientation.VERTICAL
B
Benjamin Pasero 已提交
309 310
				? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey })
				: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey });
E
Erich Gamma 已提交
311

J
Joao Moreno 已提交
312
			const onStart = Event.map(sash.onDidStart, sashEventMapper);
J
Joao Moreno 已提交
313
			const onStartDisposable = onStart(this.onSashStart, this);
J
Joao Moreno 已提交
314
			const onChange = Event.map(sash.onDidChange, sashEventMapper);
J
Joao Moreno 已提交
315
			const onChangeDisposable = onChange(this.onSashChange, this);
J
Joao Moreno 已提交
316
			const onEnd = Event.map(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
J
Joao Moreno 已提交
317
			const onEndDisposable = onEnd(this.onSashEnd, this);
J
Joao Moreno 已提交
318
			const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));
J
Joao Moreno 已提交
319

320
			const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash);
J
Joao Moreno 已提交
321
			const sashItem: ISashItem = { sash, disposable };
J
Joao 已提交
322

J
Joao Moreno 已提交
323
			this.sashItems.splice(index - 1, 0, sashItem);
J
Joao 已提交
324 325
		}

J
Joao Moreno 已提交
326
		container.appendChild(view.element);
327 328 329 330 331 332 333 334

		let highPriorityIndex: number | undefined;

		if (typeof size !== 'number' && size.type === 'split') {
			highPriorityIndex = size.index;
		}

		this.relayout(index, highPriorityIndex);
335
		this.state = State.Idle;
J
Joao Moreno 已提交
336

337
		if (typeof size !== 'number' && size.type === 'distribute') {
J
Joao Moreno 已提交
338
			this.distributeViewSizes();
J
Joao Moreno 已提交
339
		}
S
#27823  
Sandeep Somavarapu 已提交
340 341
	}

J
Joao Moreno 已提交
342
	removeView(index: number, sizing?: Sizing): IView {
343 344 345 346 347 348
		if (this.state !== State.Idle) {
			throw new Error('Cant modify splitview');
		}

		this.state = State.Busy;

J
Joao Moreno 已提交
349
		if (index < 0 || index >= this.viewItems.length) {
350
			throw new Error('Index out of bounds');
S
#27823  
Sandeep Somavarapu 已提交
351 352
		}

J
Joao Moreno 已提交
353 354
		// Remove view
		const viewItem = this.viewItems.splice(index, 1)[0];
J
Joao Moreno 已提交
355
		const view = viewItem.dispose();
E
Erich Gamma 已提交
356

J
Joao Moreno 已提交
357 358 359 360 361
		// Remove sash
		if (this.viewItems.length >= 1) {
			const sashIndex = Math.max(index - 1, 0);
			const sashItem = this.sashItems.splice(sashIndex, 1)[0];
			sashItem.disposable.dispose();
362
		}
363

J
Joao Moreno 已提交
364
		this.relayout();
365
		this.state = State.Idle;
366

J
Joao Moreno 已提交
367 368
		if (sizing && sizing.type === 'distribute') {
			this.distributeViewSizes();
J
Joao Moreno 已提交
369 370
		}

J
Joao Moreno 已提交
371
		return view;
E
Erich Gamma 已提交
372 373
	}

J
Joao Moreno 已提交
374
	moveView(from: number, to: number): void {
375 376 377 378
		if (this.state !== State.Idle) {
			throw new Error('Cant modify splitview');
		}

379 380 381
		const size = this.getViewSize(from);
		const view = this.removeView(from);
		this.addView(view, size, to);
J
Joao 已提交
382 383
	}

J
Joao Moreno 已提交
384 385 386 387 388 389 390 391 392 393 394 395 396
	swapViews(from: number, to: number): void {
		if (this.state !== State.Idle) {
			throw new Error('Cant modify splitview');
		}

		if (from > to) {
			return this.swapViews(to, from);
		}

		const fromSize = this.getViewSize(from);
		const toSize = this.getViewSize(to);
		const toView = this.removeView(to);
		const fromView = this.removeView(from);
J
Joao Moreno 已提交
397

J
Joao Moreno 已提交
398 399 400 401
		this.addView(toView, fromSize, from);
		this.addView(fromView, toSize, to);
	}

J
Joao Moreno 已提交
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
	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();
	}

423
	private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void {
J
Joao Moreno 已提交
424
		const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
J
Joao Moreno 已提交
425 426
		const lowPriorityIndexes = typeof lowPriorityIndex === 'number' ? [lowPriorityIndex] : undefined;
		const highPriorityIndexes = typeof highPriorityIndex === 'number' ? [highPriorityIndex] : undefined;
J
Joao Moreno 已提交
427

J
Joao Moreno 已提交
428
		this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndexes, highPriorityIndexes);
J
Joao Moreno 已提交
429 430 431
		this.distributeEmptySpace();
		this.layoutViews();
		this.saveProportions();
E
Erich Gamma 已提交
432 433
	}

J
Joao Moreno 已提交
434 435 436
	layout(size: number): void {
		const previousSize = Math.max(this.size, this.contentSize);
		this.size = size;
437 438

		if (!this.proportions) {
J
Joao Moreno 已提交
439
			const indexes = range(this.viewItems.length);
J
Joao Moreno 已提交
440 441
			const lowPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.Low);
			const highPriorityIndexes = indexes.filter(i => this.viewItems[i].priority === LayoutPriority.High);
J
Joao Moreno 已提交
442 443

			this.resize(this.viewItems.length - 1, size - previousSize, undefined, lowPriorityIndexes, highPriorityIndexes);
444 445 446
		} else {
			for (let i = 0; i < this.viewItems.length; i++) {
				const item = this.viewItems[i];
J
Joao Moreno 已提交
447
				item.size = clamp(Math.round(this.proportions[i] * size), item.minimumSize, item.maximumSize);
448
			}
J
Joao Moreno 已提交
449 450 451 452 453
		}

		this.distributeEmptySpace();
		this.layoutViews();
	}
454

J
Joao Moreno 已提交
455
	private saveProportions(): void {
J
Joao Moreno 已提交
456
		if (this.proportionalLayout && this.contentSize > 0) {
J
Joao Moreno 已提交
457
			this.proportions = this.viewItems.map(i => i.size / this.contentSize);
458
		}
S
Sandeep Somavarapu 已提交
459 460
	}

J
Joao Moreno 已提交
461
	private onSashStart({ sash, start, alt }: ISashEvent): void {
J
Joao Moreno 已提交
462
		const index = firstIndex(this.sashItems, item => item.sash === sash);
E
Erich Gamma 已提交
463

J
Joao Moreno 已提交
464
		// This way, we can press Alt while we resize a sash, macOS style!
465
		const disposable = combinedDisposable(
J
Joao Moreno 已提交
466 467
			domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)),
			domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false))
468
		);
J
Joao Moreno 已提交
469 470 471

		const resetSashDragState = (start: number, alt: boolean) => {
			const sizes = this.viewItems.map(i => i.size);
J
Joao Moreno 已提交
472
			let minDelta = Number.NEGATIVE_INFINITY;
J
Joao Moreno 已提交
473
			let maxDelta = Number.POSITIVE_INFINITY;
J
Joao Moreno 已提交
474

I
isidor 已提交
475 476 477
			if (this.inverseAltBehavior) {
				alt = !alt;
			}
J
Joao Moreno 已提交
478 479

			if (alt) {
480 481 482 483 484 485 486
				// When we're using the last sash with Alt, we're resizing
				// the view to the left/up, instead of right/down as usual
				// Thus, we must do the inverse of the usual
				const isLastSash = index === this.sashItems.length - 1;

				if (isLastSash) {
					const viewItem = this.viewItems[index];
J
Joao Moreno 已提交
487 488
					minDelta = (viewItem.minimumSize - viewItem.size) / 2;
					maxDelta = (viewItem.maximumSize - viewItem.size) / 2;
489 490
				} else {
					const viewItem = this.viewItems[index + 1];
J
Joao Moreno 已提交
491 492
					minDelta = (viewItem.size - viewItem.maximumSize) / 2;
					maxDelta = (viewItem.size - viewItem.minimumSize) / 2;
493
				}
J
Joao Moreno 已提交
494 495
			}

J
wip  
Joao Moreno 已提交
496 497
			let snapBefore: ISashDragSnapState | undefined;
			let snapAfter: ISashDragSnapState | undefined;
J
Joao Moreno 已提交
498 499 500 501 502

			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);
J
wip  
Joao Moreno 已提交
503
				const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].viewMaximumSize - sizes[i]), 0);
J
Joao Moreno 已提交
504
				const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].minimumSize), 0);
J
wip  
Joao Moreno 已提交
505
				const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].viewMaximumSize), 0);
J
Joao Moreno 已提交
506 507
				const minDelta = Math.max(minDeltaUp, minDeltaDown);
				const maxDelta = Math.min(maxDeltaDown, maxDeltaUp);
J
Joao Moreno 已提交
508 509
				const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
				const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
J
wip  
Joao Moreno 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528

				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
					};
J
Joao Moreno 已提交
529
				}
J
Joao Moreno 已提交
530 531
			}

J
wip  
Joao Moreno 已提交
532
			this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, snapBefore, snapAfter, disposable };
J
Joao Moreno 已提交
533 534 535
		};

		resetSashDragState(start, alt);
J
Joao Moreno 已提交
536
	}
537

J
Joao Moreno 已提交
538
	private onSashChange({ current }: ISashEvent): void {
J
wip  
Joao Moreno 已提交
539
		const { index, start, sizes, alt, minDelta, maxDelta, snapBefore, snapAfter } = this.sashDragState;
J
Joao Moreno 已提交
540 541
		this.sashDragState.current = current;

J
Joao Moreno 已提交
542
		const delta = current - start;
J
wip  
Joao Moreno 已提交
543
		const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta, snapBefore, snapAfter);
J
Joao Moreno 已提交
544 545

		if (alt) {
546
			const isLastSash = index === this.sashItems.length - 1;
J
Joao Moreno 已提交
547
			const newSizes = this.viewItems.map(i => i.size);
548 549
			const viewItemIndex = isLastSash ? index : index + 1;
			const viewItem = this.viewItems[viewItemIndex];
J
Joao Moreno 已提交
550 551
			const newMinDelta = viewItem.size - viewItem.maximumSize;
			const newMaxDelta = viewItem.size - viewItem.minimumSize;
552
			const resizeIndex = isLastSash ? index - 1 : index + 1;
553

554
			this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta);
J
Joao Moreno 已提交
555 556
		}

J
Joao Moreno 已提交
557
		this.distributeEmptySpace();
J
Joao Moreno 已提交
558
		this.layoutViews();
E
Erich Gamma 已提交
559 560
	}

J
Joao Moreno 已提交
561 562 563
	private onSashEnd(index: number): void {
		this._onDidSashChange.fire(index);
		this.sashDragState.disposable.dispose();
J
Joao Moreno 已提交
564
		this.saveProportions();
J
Joao Moreno 已提交
565 566
	}

J
Joao Moreno 已提交
567
	private onViewChange(item: ViewItem, size: number | undefined): void {
J
Joao Moreno 已提交
568
		const index = this.viewItems.indexOf(item);
E
Erich Gamma 已提交
569

J
Joao Moreno 已提交
570
		if (index < 0 || index >= this.viewItems.length) {
E
Erich Gamma 已提交
571 572 573
			return;
		}

J
Joao Moreno 已提交
574
		size = typeof size === 'number' ? size : item.size;
J
Joao Moreno 已提交
575
		size = clamp(size, item.minimumSize, item.maximumSize);
576 577 578 579 580 581 582 583 584 585 586

		if (this.inverseAltBehavior && index > 0) {
			// In this case, we want the view to grow or shrink both sides equally
			// so we just resize the "left" side by half and let `resize` do the clamping magic
			this.resize(index - 1, Math.floor((item.size - size) / 2));
			this.distributeEmptySpace();
			this.layoutViews();
		} else {
			item.size = size;
			this.relayout(index, undefined);
		}
E
Erich Gamma 已提交
587 588
	}

J
Joao Moreno 已提交
589
	resizeView(index: number, size: number): void {
590 591 592 593 594 595
		if (this.state !== State.Idle) {
			throw new Error('Cant modify splitview');
		}

		this.state = State.Busy;

J
Joao Moreno 已提交
596
		if (index < 0 || index >= this.viewItems.length) {
E
Erich Gamma 已提交
597 598 599
			return;
		}

J
Joao Moreno 已提交
600 601
		const item = this.viewItems[index];
		size = Math.round(size);
J
Joao Moreno 已提交
602
		size = clamp(size, item.minimumSize, item.maximumSize);
J
Joao Moreno 已提交
603
		let delta = size - item.size;
E
Erich Gamma 已提交
604

J
Joao Moreno 已提交
605 606
		if (delta !== 0 && index < this.viewItems.length - 1) {
			const downIndexes = range(index + 1, this.viewItems.length);
J
Joao Moreno 已提交
607 608
			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);
J
Joao Moreno 已提交
609
			const deltaDown = clamp(delta, -expandDown, collapseDown);
610

J
Joao Moreno 已提交
611
			this.resize(index, deltaDown);
J
Joao Moreno 已提交
612
			delta -= deltaDown;
613 614
		}

J
Joao Moreno 已提交
615 616
		if (delta !== 0 && index > 0) {
			const upIndexes = range(index - 1, -1);
J
Joao Moreno 已提交
617 618
			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);
J
Joao Moreno 已提交
619
			const deltaUp = clamp(-delta, -collapseUp, expandUp);
S
Sandeep Somavarapu 已提交
620

J
Joao Moreno 已提交
621
			this.resize(index - 1, deltaUp);
622
		}
623

J
Joao Moreno 已提交
624 625 626
		this.distributeEmptySpace();
		this.layoutViews();
		this.saveProportions();
627
		this.state = State.Idle;
628 629
	}

J
Joao Moreno 已提交
630 631 632 633 634 635 636 637
	distributeViewSizes(): void {
		const size = Math.floor(this.size / this.viewItems.length);

		for (let i = 0; i < this.viewItems.length - 1; i++) {
			this.resizeView(i, size);
		}
	}

J
Joao Moreno 已提交
638 639 640
	getViewSize(index: number): number {
		if (index < 0 || index >= this.viewItems.length) {
			return -1;
641 642
		}

J
Joao Moreno 已提交
643
		return this.viewItems[index].size;
644 645
	}

J
Joao Moreno 已提交
646 647 648 649
	private resize(
		index: number,
		delta: number,
		sizes = this.viewItems.map(i => i.size),
J
Joao Moreno 已提交
650 651
		lowPriorityIndexes?: number[],
		highPriorityIndexes?: number[],
J
Joao Moreno 已提交
652
		overloadMinDelta: number = Number.NEGATIVE_INFINITY,
J
Joao Moreno 已提交
653
		overloadMaxDelta: number = Number.POSITIVE_INFINITY,
J
wip  
Joao Moreno 已提交
654 655
		snapBefore?: ISashDragSnapState,
		snapAfter?: ISashDragSnapState
J
Joao Moreno 已提交
656
	): number {
J
Joao Moreno 已提交
657
		if (index < 0 || index >= this.viewItems.length) {
J
Joao Moreno 已提交
658
			return 0;
E
Erich Gamma 已提交
659 660
		}

J
Joao Moreno 已提交
661 662
		const upIndexes = range(index, -1);
		const downIndexes = range(index + 1, this.viewItems.length);
J
Joao Moreno 已提交
663

J
Joao Moreno 已提交
664 665 666 667 668
		if (highPriorityIndexes) {
			for (const index of highPriorityIndexes) {
				pushToStart(upIndexes, index);
				pushToStart(downIndexes, index);
			}
J
Joao Moreno 已提交
669
		}
670

J
Joao Moreno 已提交
671 672 673 674 675
		if (lowPriorityIndexes) {
			for (const index of lowPriorityIndexes) {
				pushToEnd(upIndexes, index);
				pushToEnd(downIndexes, index);
			}
J
Joao Moreno 已提交
676
		}
J
Joao Moreno 已提交
677

J
Joao Moreno 已提交
678 679
		const upItems = upIndexes.map(i => this.viewItems[i]);
		const upSizes = upIndexes.map(i => sizes[i]);
J
Joao Moreno 已提交
680

J
Joao Moreno 已提交
681 682
		const downItems = downIndexes.map(i => this.viewItems[i]);
		const downSizes = downIndexes.map(i => sizes[i]);
E
Erich Gamma 已提交
683

J
Joao Moreno 已提交
684 685 686 687
		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);
J
Joao Moreno 已提交
688 689
		const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta);
		const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta);
J
Joao Moreno 已提交
690

J
wip  
Joao Moreno 已提交
691 692 693 694 695 696 697 698
		let snapped = false;

		if (snapBefore) {
			const snapView = this.viewItems[snapBefore.index];
			const visible = delta >= snapBefore.limitDelta;
			snapped = visible !== snapView.visible;
			snapView.visible = visible;
		}
J
Joao Moreno 已提交
699

J
wip  
Joao Moreno 已提交
700 701 702 703 704 705
		if (!snapped && snapAfter) {
			const snapView = this.viewItems[snapAfter.index];
			const visible = delta < snapAfter.limitDelta;
			snapped = visible !== snapView.visible;
			snapView.visible = visible;
		}
J
Joao Moreno 已提交
706

J
wip  
Joao Moreno 已提交
707
		if (snapped) {
J
Joao Moreno 已提交
708 709 710
			return this.resize(index, delta, sizes, lowPriorityIndexes, highPriorityIndexes, overloadMinDelta, overloadMaxDelta);
		}

711
		delta = clamp(delta, minDelta, maxDelta);
J
Joao Moreno 已提交
712

713
		for (let i = 0, deltaUp = delta; i < upItems.length; i++) {
J
Joao Moreno 已提交
714
			const item = upItems[i];
J
Joao Moreno 已提交
715
			const size = clamp(upSizes[i] + deltaUp, item.minimumSize, item.maximumSize);
J
Joao Moreno 已提交
716
			const viewDelta = size - upSizes[i];
E
Erich Gamma 已提交
717

J
Joao Moreno 已提交
718 719 720
			deltaUp -= viewDelta;
			item.size = size;
		}
E
Erich Gamma 已提交
721

722
		for (let i = 0, deltaDown = delta; i < downItems.length; i++) {
J
Joao Moreno 已提交
723
			const item = downItems[i];
J
Joao Moreno 已提交
724
			const size = clamp(downSizes[i] - deltaDown, item.minimumSize, item.maximumSize);
J
Joao Moreno 已提交
725
			const viewDelta = size - downSizes[i];
E
Erich Gamma 已提交
726

J
Joao Moreno 已提交
727 728
			deltaDown += viewDelta;
			item.size = size;
E
Erich Gamma 已提交
729 730
		}

J
Joao Moreno 已提交
731 732 733
		return delta;
	}

J
Joao Moreno 已提交
734 735
	private distributeEmptySpace(lowPriorityIndex?: number): void {
		const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
J
Joao Moreno 已提交
736
		let emptyDelta = this.size - contentSize;
E
Erich Gamma 已提交
737

J
Joao Moreno 已提交
738 739 740 741 742 743 744 745
		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]];
J
Joao Moreno 已提交
746
			const size = clamp(item.size + emptyDelta, item.minimumSize, item.maximumSize);
J
Joao Moreno 已提交
747 748 749 750
			const viewDelta = size - item.size;

			emptyDelta -= viewDelta;
			item.size = size;
E
Erich Gamma 已提交
751
		}
J
Joao Moreno 已提交
752
	}
E
Erich Gamma 已提交
753

J
Joao Moreno 已提交
754
	private layoutViews(): void {
755
		// Save new content size
J
Joao Moreno 已提交
756 757
		this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);

758
		// Layout views
J
Joao Moreno 已提交
759
		this.viewItems.forEach(item => item.layout());
760 761

		// Layout sashes
J
Joao Moreno 已提交
762
		this.sashItems.forEach(item => item.sash.layout());
E
Erich Gamma 已提交
763 764

		// Update sashes enablement
B
Benjamin Pasero 已提交
765
		let previous = false;
J
Joao Moreno 已提交
766
		const collapsesDown = this.viewItems.map(i => previous = (i.size - i.minimumSize > 0) || previous);
E
Erich Gamma 已提交
767 768

		previous = false;
J
Joao Moreno 已提交
769
		const expandsDown = this.viewItems.map(i => previous = (i.maximumSize - i.size > 0) || previous);
E
Erich Gamma 已提交
770

J
Joao Moreno 已提交
771
		const reverseViews = [...this.viewItems].reverse();
E
Erich Gamma 已提交
772
		previous = false;
J
Joao Moreno 已提交
773
		const collapsesUp = reverseViews.map(i => previous = (i.size - i.minimumSize > 0) || previous).reverse();
E
Erich Gamma 已提交
774 775

		previous = false;
J
Joao Moreno 已提交
776
		const expandsUp = reverseViews.map(i => previous = (i.maximumSize - i.size > 0) || previous).reverse();
E
Erich Gamma 已提交
777

J
Joao Moreno 已提交
778 779 780
		this.sashItems.forEach(({ sash }, index) => {
			const min = !(collapsesDown[index] && expandsUp[index + 1]);
			const max = !(expandsDown[index] && collapsesUp[index + 1]);
J
wip  
Joao Moreno 已提交
781

J
Joao Moreno 已提交
782 783 784 785 786 787 788 789 790 791
			if (min && max) {
				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;
J
wip  
Joao Moreno 已提交
792
				} else {
J
Joao Moreno 已提交
793
					sash.state = SashState.Disabled;
J
wip  
Joao Moreno 已提交
794
				}
J
Joao Moreno 已提交
795 796 797 798
			} else if (min && !max) {
				sash.state = SashState.Minimum;
			} else if (!min && max) {
				sash.state = SashState.Maximum;
J
Joao Moreno 已提交
799
			} else {
J
Joao Moreno 已提交
800
				sash.state = SashState.Enabled;
J
Joao Moreno 已提交
801
			}
J
Joao Moreno 已提交
802
			// }
E
Erich Gamma 已提交
803 804 805
		});
	}

J
Joao Moreno 已提交
806
	private getSashPosition(sash: Sash): number {
B
Benjamin Pasero 已提交
807
		let position = 0;
E
Erich Gamma 已提交
808

J
Joao Moreno 已提交
809 810 811 812 813 814
		for (let i = 0; i < this.sashItems.length; i++) {
			position += this.viewItems[i].size;

			if (this.sashItems[i].sash === sash) {
				return position;
			}
E
Erich Gamma 已提交
815 816
		}

J
Joao Moreno 已提交
817
		return 0;
E
Erich Gamma 已提交
818 819
	}

J
Joao Moreno 已提交
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
	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;
	}

J
Joao Moreno 已提交
842
	dispose(): void {
843 844
		super.dispose();

J
Joao Moreno 已提交
845
		this.viewItems.forEach(i => i.dispose());
J
Joao Moreno 已提交
846 847 848 849
		this.viewItems = [];

		this.sashItems.forEach(i => i.disposable.dispose());
		this.sashItems = [];
E
Erich Gamma 已提交
850 851
	}
}