sash.ts 10.4 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
}

export interface ISashOptions {
	orientation?: Orientation;
}

export enum Orientation {
	VERTICAL,
	HORIZONTAL
}

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

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

J
Joao Moreno 已提交
58 59 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
	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 已提交
86

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

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

J
Joao Moreno 已提交
94 95 96 97 98
		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 已提交
99

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

B
Benjamin Pasero 已提交
105
		this.setOrientation(options.orientation || Orientation.VERTICAL);
E
Erich Gamma 已提交
106 107 108 109 110

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

J
Joao Moreno 已提交
111
	setOrientation(orientation: Orientation): void {
B
Benjamin Pasero 已提交
112 113 114
		this.orientation = orientation;

		if (this.orientation === Orientation.HORIZONTAL) {
J
Joao Moreno 已提交
115 116
			addClass(this.el, 'horizontal');
			removeClass(this.el, 'vertical');
B
Benjamin Pasero 已提交
117
		} else {
J
Joao Moreno 已提交
118 119
			removeClass(this.el, 'horizontal');
			addClass(this.el, 'vertical');
B
Benjamin Pasero 已提交
120 121 122 123 124 125 126
		}

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

E
Erich Gamma 已提交
127
	private onMouseDown(e: MouseEvent): void {
128
		EventHelper.stop(e, false);
E
Erich Gamma 已提交
129

J
Joao Moreno 已提交
130
		if (!this.enabled) {
E
Erich Gamma 已提交
131 132 133
			return;
		}

J
Joao Moreno 已提交
134 135 136
		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
137
		}
138

139 140 141
		const mouseDownEvent = new StandardMouseEvent(e);
		const startX = mouseDownEvent.posx;
		const startY = mouseDownEvent.posy;
142
		const altKey = mouseDownEvent.altKey;
E
Erich Gamma 已提交
143

144
		const startEvent: ISashEvent = {
E
Erich Gamma 已提交
145 146 147
			startX: startX,
			currentX: startX,
			startY: startY,
148 149
			currentY: startY,
			altKey
E
Erich Gamma 已提交
150 151
		};

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

155
		// fix https://github.com/Microsoft/vscode/issues/21675
J
Joao Moreno 已提交
156
		const globalStyle = createStyleSheet(this.el);
157 158 159 160 161
		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 已提交
162

J
Joao Moreno 已提交
163 164 165
		const disposables: IDisposable[] = [];

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

169
			const event: ISashEvent = {
E
Erich Gamma 已提交
170 171 172
				startX: startX,
				currentX: mouseMoveEvent.posx,
				startY: startY,
173 174
				currentY: mouseMoveEvent.posy,
				altKey
E
Erich Gamma 已提交
175 176
			};

I
isidor 已提交
177
			this._onDidChange.fire(event);
J
Joao Moreno 已提交
178 179 180
		};

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

J
Joao Moreno 已提交
183
			this.el.removeChild(globalStyle);
184

J
Joao Moreno 已提交
185
			removeClass(this.el, 'active');
I
isidor 已提交
186
			this._onDidEnd.fire();
E
Erich Gamma 已提交
187

J
Joao Moreno 已提交
188
			dispose(disposables);
189

J
Joao Moreno 已提交
190 191 192
			const iframes = getElementsByTagName('iframe');
			for (const iframe of iframes) {
				iframe.style.pointerEvents = 'auto';
193
			}
J
Joao Moreno 已提交
194 195 196 197 198 199 200 201
		};

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

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

B
Benjamin Pasero 已提交
204
	private onTouchStart(event: GestureEvent): void {
205
		EventHelper.stop(event);
E
Erich Gamma 已提交
206

207
		const listeners: IDisposable[] = [];
E
Erich Gamma 已提交
208

209 210
		const startX = event.pageX;
		const startY = event.pageY;
211 212
		const altKey = event.altKey;

E
Erich Gamma 已提交
213

I
isidor 已提交
214
		this._onDidStart.fire({
E
Erich Gamma 已提交
215 216 217
			startX: startX,
			currentX: startX,
			startY: startY,
218 219
			currentY: startY,
			altKey
E
Erich Gamma 已提交
220 221
		});

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

J
Joao Moreno 已提交
234
		listeners.push(addDisposableListener(this.el, EventType.End, (event: GestureEvent) => {
I
isidor 已提交
235
			this._onDidEnd.fire();
J
Joao Moreno 已提交
236
			dispose(listeners);
E
Erich Gamma 已提交
237 238 239
		}));
	}

J
Joao Moreno 已提交
240
	layout(): void {
J
Joao Moreno 已提交
241 242
		const size = isIPad ? 20 : 5;

E
Erich Gamma 已提交
243
		if (this.orientation === Orientation.VERTICAL) {
244
			const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
J
Joao Moreno 已提交
245
			this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (size / 2) + 'px';
E
Erich Gamma 已提交
246 247

			if (verticalProvider.getVerticalSashTop) {
J
Joao Moreno 已提交
248
				this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
E
Erich Gamma 已提交
249 250 251
			}

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

			if (horizontalProvider.getHorizontalSashLeft) {
J
Joao Moreno 已提交
259
				this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
E
Erich Gamma 已提交
260 261 262
			}

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

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

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

J
Joao Moreno 已提交
280
	isHidden(): boolean {
E
Erich Gamma 已提交
281 282 283
		return this.hidden;
	}

J
Joao Moreno 已提交
284 285 286
	dispose(): void {
		if (this.el && this.el.parentElement) {
			this.el.parentElement.removeChild(this.el);
E
Erich Gamma 已提交
287
		}
J
Joao Moreno 已提交
288 289 290

		this.el = null;
		this.disposables = dispose(this.disposables);
E
Erich Gamma 已提交
291 292
	}
}
S
Sandeep Somavarapu 已提交
293 294 295 296 297 298 299 300 301 302 303 304

/**
 * 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 已提交
305
	private readonly _onPositionChange: Emitter<number> = new Emitter<number>();
J
Joao Moreno 已提交
306
	get onPositionChange(): Event<number> { return this._onPositionChange.event; }
S
Sandeep Somavarapu 已提交
307

B
Benjamin Pasero 已提交
308
	constructor(container: HTMLElement, private minWidth: number) {
S
Sandeep Somavarapu 已提交
309
		super();
310

S
Sandeep Somavarapu 已提交
311 312 313
		this.ratio = 0.5;
		this.sash = new Sash(container, this);

I
isidor 已提交
314 315 316 317
		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 已提交
318 319
	}

J
Joao Moreno 已提交
320
	getVerticalSashTop(): number {
S
Sandeep Somavarapu 已提交
321 322 323
		return 0;
	}

J
Joao Moreno 已提交
324
	getVerticalSashLeft(): number {
S
Sandeep Somavarapu 已提交
325 326 327
		return this.position;
	}

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

J
Joao Moreno 已提交
332
	setDimenesion(dimension: Dimension) {
S
Sandeep Somavarapu 已提交
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
		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 已提交
356
		this.compute(0.5);
S
Sandeep Somavarapu 已提交
357 358 359 360 361
		this._onPositionChange.fire(this.position);
		this.sash.layout();
	}

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

		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();
		}
	}
381
}