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

'use strict';

import 'vs/css!./sash';
S
Sandeep Somavarapu 已提交
9
import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
10 11
import { isIPad } from 'vs/base/browser/browser';
import { isMacintosh } from 'vs/base/common/platform';
12
import * as types from 'vs/base/common/types';
13
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
J
Johannes Rieken 已提交
14
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
M
Matt Bierner 已提交
15
import { Event, Emitter } from 'vs/base/common/event';
J
Joao Moreno 已提交
16 17
import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, Dimension, append, $, addClass, removeClass } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
E
Erich Gamma 已提交
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

export interface ISashLayoutProvider { }

export interface IVerticalSashLayoutProvider extends ISashLayoutProvider {
	getVerticalSashLeft(sash: Sash): number;
	getVerticalSashTop?(sash: Sash): number;
	getVerticalSashHeight?(sash: Sash): number;
}

export interface IHorizontalSashLayoutProvider extends ISashLayoutProvider {
	getHorizontalSashTop(sash: Sash): number;
	getHorizontalSashLeft?(sash: Sash): number;
	getHorizontalSashWidth?(sash: Sash): number;
}

export interface ISashEvent {
	startX: number;
	currentX: number;
	startY: number;
	currentY: number;
38
	altKey: boolean;
E
Erich Gamma 已提交
39 40 41 42 43 44 45 46 47 48 49 50
}

export interface ISashOptions {
	baseSize?: number;
	orientation?: Orientation;
}

export enum Orientation {
	VERTICAL,
	HORIZONTAL
}

I
isidor 已提交
51
export class Sash {
E
Erich Gamma 已提交
52

J
Joao Moreno 已提交
53
	private el: HTMLElement;
E
Erich Gamma 已提交
54 55 56 57
	private layoutProvider: ISashLayoutProvider;
	private hidden: boolean;
	private orientation: Orientation;
	private size: number;
J
Joao Moreno 已提交
58
	private disposables: IDisposable[] = [];
E
Erich Gamma 已提交
59

J
Joao Moreno 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
	private _enabled = true;
	get enabled(): boolean { return this._enabled; }
	set enabled(enabled: boolean) {
		this._enabled = enabled;

		if (enabled) {
			removeClass(this.el, 'disabled');
		} else {
			addClass(this.el, 'disabled');
		}

		this._onDidEnablementChange.fire(enabled);
	}

	private readonly _onDidEnablementChange = new Emitter<boolean>();
	readonly onDidEnablementChange: Event<boolean> = this._onDidEnablementChange.event;

	private readonly _onDidStart = new Emitter<ISashEvent>();
	readonly onDidStart: Event<ISashEvent> = this._onDidStart.event;

	private readonly _onDidChange = new Emitter<ISashEvent>();
	readonly onDidChange: Event<ISashEvent> = this._onDidChange.event;

	private readonly _onDidReset = new Emitter<void>();
	readonly onDidReset: Event<void> = this._onDidReset.event;

	private readonly _onDidEnd = new Emitter<void>();
	readonly onDidEnd: Event<void> = this._onDidEnd.event;
I
isidor 已提交
88

E
Erich Gamma 已提交
89
	constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions = {}) {
J
Joao Moreno 已提交
90
		this.el = append(container, $('.monaco-sash'));
E
Erich Gamma 已提交
91

B
Benjamin Pasero 已提交
92
		if (isMacintosh) {
J
Joao Moreno 已提交
93
			addClass(this.el, 'mac');
B
Benjamin Pasero 已提交
94 95
		}

J
Joao Moreno 已提交
96 97 98 99 100
		domEvent(this.el, 'mousedown')(this.onMouseDown, this, this.disposables);
		domEvent(this.el, 'dblclick')(this.onMouseDoubleClick, this, this.disposables);

		Gesture.addTarget(this.el);
		domEvent(this.el, EventType.Start)(this.onTouchStart, this, this.disposables);
E
Erich Gamma 已提交
101 102 103

		this.size = options.baseSize || 5;

B
Benjamin Pasero 已提交
104
		if (isIPad) {
E
Erich Gamma 已提交
105
			this.size *= 4; // see also http://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
J
Joao Moreno 已提交
106
			addClass(this.el, 'touch');
E
Erich Gamma 已提交
107 108
		}

B
Benjamin Pasero 已提交
109
		this.setOrientation(options.orientation || Orientation.VERTICAL);
E
Erich Gamma 已提交
110 111 112 113 114

		this.hidden = false;
		this.layoutProvider = layoutProvider;
	}

J
Joao Moreno 已提交
115
	setOrientation(orientation: Orientation): void {
B
Benjamin Pasero 已提交
116 117 118
		this.orientation = orientation;

		if (this.orientation === Orientation.HORIZONTAL) {
J
Joao Moreno 已提交
119 120 121
			addClass(this.el, 'horizontal');
			removeClass(this.el, 'vertical');
			this.el.style.height = `${this.size}px`;
B
Benjamin Pasero 已提交
122
		} else {
J
Joao Moreno 已提交
123 124 125
			removeClass(this.el, 'horizontal');
			addClass(this.el, 'vertical');
			this.el.style.width = `${this.size}px`;
B
Benjamin Pasero 已提交
126 127 128 129 130 131 132
		}

		if (this.layoutProvider) {
			this.layout();
		}
	}

E
Erich Gamma 已提交
133
	private onMouseDown(e: MouseEvent): void {
134
		EventHelper.stop(e, false);
E
Erich Gamma 已提交
135

J
Joao Moreno 已提交
136
		if (!this.enabled) {
E
Erich Gamma 已提交
137 138 139
			return;
		}

J
Joao Moreno 已提交
140 141 142
		const iframes = getElementsByTagName('iframe');
		for (const iframe of iframes) {
			iframe.style.pointerEvents = 'none'; // disable mouse events on iframes as long as we drag the sash
143
		}
144

145 146 147
		const mouseDownEvent = new StandardMouseEvent(e);
		const startX = mouseDownEvent.posx;
		const startY = mouseDownEvent.posy;
148
		const altKey = mouseDownEvent.altKey;
E
Erich Gamma 已提交
149

150
		const startEvent: ISashEvent = {
E
Erich Gamma 已提交
151 152 153
			startX: startX,
			currentX: startX,
			startY: startY,
154 155
			currentY: startY,
			altKey
E
Erich Gamma 已提交
156 157
		};

J
Joao Moreno 已提交
158
		addClass(this.el, 'active');
I
isidor 已提交
159
		this._onDidStart.fire(startEvent);
E
Erich Gamma 已提交
160

161
		// fix https://github.com/Microsoft/vscode/issues/21675
J
Joao Moreno 已提交
162
		const globalStyle = createStyleSheet(this.el);
163 164 165 166 167
		if (this.orientation === Orientation.HORIZONTAL) {
			globalStyle.innerHTML = `* { cursor: ${isMacintosh ? 'row-resize' : 'ns-resize'}; }`;
		} else {
			globalStyle.innerHTML = `* { cursor: ${isMacintosh ? 'col-resize' : 'ew-resize'}; }`;
		}
E
Erich Gamma 已提交
168

J
Joao Moreno 已提交
169 170 171
		const disposables: IDisposable[] = [];

		const onMouseMove = (e: MouseEvent) => {
172
			EventHelper.stop(e, false);
173
			const mouseMoveEvent = new StandardMouseEvent(e as MouseEvent);
E
Erich Gamma 已提交
174

175
			const event: ISashEvent = {
E
Erich Gamma 已提交
176 177 178
				startX: startX,
				currentX: mouseMoveEvent.posx,
				startY: startY,
179 180
				currentY: mouseMoveEvent.posy,
				altKey
E
Erich Gamma 已提交
181 182
			};

I
isidor 已提交
183
			this._onDidChange.fire(event);
J
Joao Moreno 已提交
184 185 186
		};

		const onMouseUp = (e: MouseEvent) => {
187
			EventHelper.stop(e, false);
188

J
Joao Moreno 已提交
189
			this.el.removeChild(globalStyle);
190

J
Joao Moreno 已提交
191
			removeClass(this.el, 'active');
I
isidor 已提交
192
			this._onDidEnd.fire();
E
Erich Gamma 已提交
193

J
Joao Moreno 已提交
194
			dispose(disposables);
195

J
Joao Moreno 已提交
196 197 198
			const iframes = getElementsByTagName('iframe');
			for (const iframe of iframes) {
				iframe.style.pointerEvents = 'auto';
199
			}
J
Joao Moreno 已提交
200 201 202 203 204 205 206 207
		};

		domEvent(window, 'mousemove')(onMouseMove, null, disposables);
		domEvent(window, 'mouseup')(onMouseUp, null, disposables);
	}

	private onMouseDoubleClick(event: MouseEvent): void {
		this._onDidReset.fire();
E
Erich Gamma 已提交
208 209
	}

B
Benjamin Pasero 已提交
210
	private onTouchStart(event: GestureEvent): void {
211
		EventHelper.stop(event);
E
Erich Gamma 已提交
212

213
		const listeners: IDisposable[] = [];
E
Erich Gamma 已提交
214

215 216
		const startX = event.pageX;
		const startY = event.pageY;
217 218
		const altKey = event.altKey;

E
Erich Gamma 已提交
219

I
isidor 已提交
220
		this._onDidStart.fire({
E
Erich Gamma 已提交
221 222 223
			startX: startX,
			currentX: startX,
			startY: startY,
224 225
			currentY: startY,
			altKey
E
Erich Gamma 已提交
226 227
		});

J
Joao Moreno 已提交
228
		listeners.push(addDisposableListener(this.el, EventType.Change, (event: GestureEvent) => {
B
Benjamin Pasero 已提交
229
			if (types.isNumber(event.pageX) && types.isNumber(event.pageY)) {
I
isidor 已提交
230
				this._onDidChange.fire({
E
Erich Gamma 已提交
231 232 233
					startX: startX,
					currentX: event.pageX,
					startY: startY,
234 235
					currentY: event.pageY,
					altKey
E
Erich Gamma 已提交
236 237 238 239
				});
			}
		}));

J
Joao Moreno 已提交
240
		listeners.push(addDisposableListener(this.el, EventType.End, (event: GestureEvent) => {
I
isidor 已提交
241
			this._onDidEnd.fire();
J
Joao Moreno 已提交
242
			dispose(listeners);
E
Erich Gamma 已提交
243 244 245
		}));
	}

J
Joao Moreno 已提交
246
	layout(): void {
E
Erich Gamma 已提交
247
		if (this.orientation === Orientation.VERTICAL) {
248
			const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
J
Joao Moreno 已提交
249
			this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px';
E
Erich Gamma 已提交
250 251

			if (verticalProvider.getVerticalSashTop) {
J
Joao Moreno 已提交
252
				this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
E
Erich Gamma 已提交
253 254 255
			}

			if (verticalProvider.getVerticalSashHeight) {
J
Joao Moreno 已提交
256
				this.el.style.height = verticalProvider.getVerticalSashHeight(this) + 'px';
E
Erich Gamma 已提交
257 258
			}
		} else {
259
			const horizontalProvider = (<IHorizontalSashLayoutProvider>this.layoutProvider);
J
Joao Moreno 已提交
260
			this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px';
E
Erich Gamma 已提交
261 262

			if (horizontalProvider.getHorizontalSashLeft) {
J
Joao Moreno 已提交
263
				this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
E
Erich Gamma 已提交
264 265 266
			}

			if (horizontalProvider.getHorizontalSashWidth) {
J
Joao Moreno 已提交
267
				this.el.style.width = horizontalProvider.getHorizontalSashWidth(this) + 'px';
E
Erich Gamma 已提交
268 269 270 271
			}
		}
	}

J
Joao Moreno 已提交
272
	show(): void {
E
Erich Gamma 已提交
273
		this.hidden = false;
J
Joao Moreno 已提交
274 275
		this.el.style.removeProperty('display');
		this.el.setAttribute('aria-hidden', 'false');
E
Erich Gamma 已提交
276 277
	}

J
Joao Moreno 已提交
278
	hide(): void {
E
Erich Gamma 已提交
279
		this.hidden = true;
J
Joao Moreno 已提交
280 281
		this.el.style.display = 'none';
		this.el.setAttribute('aria-hidden', 'true');
E
Erich Gamma 已提交
282 283
	}

J
Joao Moreno 已提交
284
	isHidden(): boolean {
E
Erich Gamma 已提交
285 286 287
		return this.hidden;
	}

J
Joao Moreno 已提交
288 289 290
	dispose(): void {
		if (this.el && this.el.parentElement) {
			this.el.parentElement.removeChild(this.el);
E
Erich Gamma 已提交
291
		}
J
Joao Moreno 已提交
292 293 294

		this.el = null;
		this.disposables = dispose(this.disposables);
E
Erich Gamma 已提交
295 296
	}
}
S
Sandeep Somavarapu 已提交
297 298 299 300 301 302 303 304 305 306 307 308

/**
 * A simple Vertical Sash that computes the position of the sash when it is moved between the given dimension.
 * Triggers onPositionChange event when the position is changed
 */
export class VSash extends Disposable implements IVerticalSashLayoutProvider {
	private sash: Sash;
	private ratio: number;
	private startPosition: number;
	private position: number;
	private dimension: Dimension;

M
Matt Bierner 已提交
309
	private readonly _onPositionChange: Emitter<number> = new Emitter<number>();
J
Joao Moreno 已提交
310
	get onPositionChange(): Event<number> { return this._onPositionChange.event; }
S
Sandeep Somavarapu 已提交
311

B
Benjamin Pasero 已提交
312
	constructor(container: HTMLElement, private minWidth: number) {
S
Sandeep Somavarapu 已提交
313
		super();
314

S
Sandeep Somavarapu 已提交
315 316 317
		this.ratio = 0.5;
		this.sash = new Sash(container, this);

I
isidor 已提交
318 319 320 321
		this._register(this.sash.onDidStart(() => this.onSashDragStart()));
		this._register(this.sash.onDidChange((e: ISashEvent) => this.onSashDrag(e)));
		this._register(this.sash.onDidEnd(() => this.onSashDragEnd()));
		this._register(this.sash.onDidReset(() => this.onSashReset()));
S
Sandeep Somavarapu 已提交
322 323
	}

J
Joao Moreno 已提交
324
	getVerticalSashTop(): number {
S
Sandeep Somavarapu 已提交
325 326 327
		return 0;
	}

J
Joao Moreno 已提交
328
	getVerticalSashLeft(): number {
S
Sandeep Somavarapu 已提交
329 330 331
		return this.position;
	}

J
Joao Moreno 已提交
332
	getVerticalSashHeight(): number {
S
Sandeep Somavarapu 已提交
333 334 335
		return this.dimension.height;
	}

J
Joao Moreno 已提交
336
	setDimenesion(dimension: Dimension) {
S
Sandeep Somavarapu 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
		this.dimension = dimension;
		this.compute(this.ratio);
	}

	private onSashDragStart(): void {
		this.startPosition = this.position;
	}

	private onSashDrag(e: ISashEvent): void {
		this.compute((this.startPosition + (e.currentX - e.startX)) / this.dimension.width);
	}

	private compute(ratio: number) {
		this.computeSashPosition(ratio);
		this.ratio = this.position / this.dimension.width;
		this._onPositionChange.fire(this.position);
	}

	private onSashDragEnd(): void {
		this.sash.layout();
	}

	private onSashReset(): void {
S
Sandeep Somavarapu 已提交
360
		this.compute(0.5);
S
Sandeep Somavarapu 已提交
361 362 363 364 365
		this._onPositionChange.fire(this.position);
		this.sash.layout();
	}

	private computeSashPosition(sashRatio: number = this.ratio) {
366
		const contentWidth = this.dimension.width;
S
Sandeep Somavarapu 已提交
367
		let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth);
368
		const midPoint = Math.floor(0.5 * contentWidth);
S
Sandeep Somavarapu 已提交
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384

		if (contentWidth > this.minWidth * 2) {
			if (sashPosition < this.minWidth) {
				sashPosition = this.minWidth;
			}
			if (sashPosition > contentWidth - this.minWidth) {
				sashPosition = contentWidth - this.minWidth;
			}
		} else {
			sashPosition = midPoint;
		}
		if (this.position !== sashPosition) {
			this.position = sashPosition;
			this.sash.layout();
		}
	}
385
}