sash.ts 11.5 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!./sash';
7
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
8 9
import { isIPad } from 'vs/base/browser/browser';
import { isMacintosh } from 'vs/base/common/platform';
10
import * as types from 'vs/base/common/types';
11
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
J
Johannes Rieken 已提交
12
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
13
import { Event, Emitter } from 'vs/base/common/event';
14
import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom';
15
import { domEvent } from 'vs/base/browser/event';
E
Erich Gamma 已提交
16

17
const DEBUG = false;
J
Joao Moreno 已提交
18

E
Erich Gamma 已提交
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
}

export interface ISashOptions {
	orientation?: Orientation;
43 44
	orthogonalStartSash?: Sash;
	orthogonalEndSash?: Sash;
E
Erich Gamma 已提交
45 46
}

47
export const enum Orientation {
E
Erich Gamma 已提交
48 49 50 51
	VERTICAL,
	HORIZONTAL
}

52
export const enum SashState {
J
Joao Moreno 已提交
53 54 55 56 57 58
	Disabled,
	Minimum,
	Maximum,
	Enabled
}

59
export class Sash extends Disposable {
E
Erich Gamma 已提交
60

61
	private el: HTMLElement;
E
Erich Gamma 已提交
62 63 64 65
	private layoutProvider: ISashLayoutProvider;
	private hidden: boolean;
	private orientation: Orientation;

J
Joao Moreno 已提交
66 67 68
	private _state: SashState = SashState.Enabled;
	get state(): SashState { return this._state; }
	set state(state: SashState) {
69 70 71
		if (this._state === state) {
			return;
		}
72

J
Joao Moreno 已提交
73 74 75
		toggleClass(this.el, 'disabled', state === SashState.Disabled);
		toggleClass(this.el, 'minimum', state === SashState.Minimum);
		toggleClass(this.el, 'maximum', state === SashState.Maximum);
76

77
		this._state = state;
J
Joao Moreno 已提交
78
		this._onDidEnablementChange.fire(state);
79 80
	}

81
	private readonly _onDidEnablementChange = this._register(new Emitter<SashState>());
J
Joao Moreno 已提交
82
	readonly onDidEnablementChange: Event<SashState> = this._onDidEnablementChange.event;
83

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

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

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

93
	private readonly _onDidEnd = this._register(new Emitter<void>());
94 95
	readonly onDidEnd: Event<void> = this._onDidEnd.event;

J
Joao Moreno 已提交
96 97
	linkedSash: Sash | undefined = undefined;

98 99 100 101 102
	private orthogonalStartSashDisposables: IDisposable[] = [];
	private _orthogonalStartSash: Sash | undefined;
	get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
	set orthogonalStartSash(sash: Sash | undefined) {
		this.orthogonalStartSashDisposables = dispose(this.orthogonalStartSashDisposables);
103

104 105 106
		if (sash) {
			sash.onDidEnablementChange(this.onOrthogonalStartSashEnablementChange, this, this.orthogonalStartSashDisposables);
			this.onOrthogonalStartSashEnablementChange(sash.state);
107
		} else {
108
			this.onOrthogonalStartSashEnablementChange(SashState.Disabled);
109 110
		}

111
		this._orthogonalStartSash = sash;
112 113
	}

114 115 116 117 118
	private orthogonalEndSashDisposables: IDisposable[] = [];
	private _orthogonalEndSash: Sash | undefined;
	get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
	set orthogonalEndSash(sash: Sash | undefined) {
		this.orthogonalEndSashDisposables = dispose(this.orthogonalEndSashDisposables);
119

120 121 122
		if (sash) {
			sash.onDidEnablementChange(this.onOrthogonalEndSashEnablementChange, this, this.orthogonalEndSashDisposables);
			this.onOrthogonalEndSashEnablementChange(sash.state);
123
		} else {
124
			this.onOrthogonalEndSashEnablementChange(SashState.Disabled);
125 126
		}

127
		this._orthogonalEndSash = sash;
128
	}
J
Joao Moreno 已提交
129

E
Erich Gamma 已提交
130
	constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions = {}) {
131 132
		super();

133
		this.el = append(container, $('.monaco-sash'));
E
Erich Gamma 已提交
134

B
Benjamin Pasero 已提交
135
		if (isMacintosh) {
136
			addClass(this.el, 'mac');
B
Benjamin Pasero 已提交
137 138
		}

139 140
		this._register(domEvent(this.el, 'mousedown')(this.onMouseDown, this));
		this._register(domEvent(this.el, 'dblclick')(this.onMouseDoubleClick, this));
J
Joao Moreno 已提交
141

142
		Gesture.addTarget(this.el);
143
		this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this));
E
Erich Gamma 已提交
144

B
Benjamin Pasero 已提交
145
		if (isIPad) {
146 147
			// see also http://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
			addClass(this.el, 'touch');
E
Erich Gamma 已提交
148 149
		}

B
Benjamin Pasero 已提交
150
		this.setOrientation(options.orientation || Orientation.VERTICAL);
E
Erich Gamma 已提交
151 152 153

		this.hidden = false;
		this.layoutProvider = layoutProvider;
J
Joao Moreno 已提交
154

155 156
		this.orthogonalStartSash = options.orthogonalStartSash;
		this.orthogonalEndSash = options.orthogonalEndSash;
J
Joao Moreno 已提交
157 158

		toggleClass(this.el, 'debug', DEBUG);
E
Erich Gamma 已提交
159 160
	}

161
	setOrientation(orientation: Orientation): void {
B
Benjamin Pasero 已提交
162 163 164
		this.orientation = orientation;

		if (this.orientation === Orientation.HORIZONTAL) {
165 166
			addClass(this.el, 'horizontal');
			removeClass(this.el, 'vertical');
B
Benjamin Pasero 已提交
167
		} else {
168 169
			removeClass(this.el, 'horizontal');
			addClass(this.el, 'vertical');
B
Benjamin Pasero 已提交
170 171 172 173 174 175 176
		}

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

E
Erich Gamma 已提交
177
	private onMouseDown(e: MouseEvent): void {
178
		EventHelper.stop(e, false);
E
Erich Gamma 已提交
179

J
Joao Moreno 已提交
180 181
		let isMultisashResize = false;

J
Joao Moreno 已提交
182 183 184 185 186
		if (this.linkedSash && !(e as any).__linkedSashEvent) {
			(e as any).__linkedSashEvent = true;
			this.linkedSash.onMouseDown(e);
		}

187 188
		if (!(e as any).__orthogonalSashEvent) {
			let orthogonalSash: Sash | undefined;
189 190

			if (this.orientation === Orientation.VERTICAL) {
J
Joao Moreno 已提交
191
				if (e.offsetY <= 4) {
192
					orthogonalSash = this.orthogonalStartSash;
J
Joao Moreno 已提交
193
				} else if (e.offsetY >= this.el.clientHeight - 4) {
194
					orthogonalSash = this.orthogonalEndSash;
195 196
				}
			} else {
J
Joao Moreno 已提交
197
				if (e.offsetX <= 4) {
198
					orthogonalSash = this.orthogonalStartSash;
J
Joao Moreno 已提交
199
				} else if (e.offsetX >= this.el.clientWidth - 4) {
200
					orthogonalSash = this.orthogonalEndSash;
201 202 203
				}
			}

204 205 206 207
			if (orthogonalSash) {
				isMultisashResize = true;
				(e as any).__orthogonalSashEvent = true;
				orthogonalSash.onMouseDown(e);
208 209 210
			}
		}

J
Joao Moreno 已提交
211
		if (!this.state) {
E
Erich Gamma 已提交
212 213 214
			return;
		}

215 216 217
		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
218
		}
219

220 221 222
		const mouseDownEvent = new StandardMouseEvent(e);
		const startX = mouseDownEvent.posx;
		const startY = mouseDownEvent.posy;
223
		const altKey = mouseDownEvent.altKey;
J
Joao Moreno 已提交
224
		const startEvent: ISashEvent = { startX, currentX: startX, startY, currentY: startY, altKey };
E
Erich Gamma 已提交
225

226
		addClass(this.el, 'active');
I
isidor 已提交
227
		this._onDidStart.fire(startEvent);
E
Erich Gamma 已提交
228

229
		// fix https://github.com/Microsoft/vscode/issues/21675
J
Joao Moreno 已提交
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
		const style = createStyleSheet(this.el);
		const updateStyle = () => {
			let cursor = '';

			if (isMultisashResize) {
				cursor = 'all-scroll';
			} else if (this.orientation === Orientation.HORIZONTAL) {
				if (this.state === SashState.Minimum) {
					cursor = 's-resize';
				} else if (this.state === SashState.Maximum) {
					cursor = 'n-resize';
				} else {
					cursor = isMacintosh ? 'row-resize' : 'ns-resize';
				}
			} else {
				if (this.state === SashState.Minimum) {
					cursor = 'e-resize';
				} else if (this.state === SashState.Maximum) {
					cursor = 'w-resize';
				} else {
					cursor = isMacintosh ? 'col-resize' : 'ew-resize';
				}
			}

			style.innerHTML = `* { cursor: ${cursor} !important; }`;
		};
E
Erich Gamma 已提交
256

257 258
		const disposables: IDisposable[] = [];

J
Joao Moreno 已提交
259 260 261 262 263 264
		updateStyle();

		if (!isMultisashResize) {
			this.onDidEnablementChange(updateStyle, null, disposables);
		}

265
		const onMouseMove = (e: MouseEvent) => {
266
			EventHelper.stop(e, false);
267
			const mouseMoveEvent = new StandardMouseEvent(e as MouseEvent);
J
Joao Moreno 已提交
268
			const event: ISashEvent = { startX, currentX: mouseMoveEvent.posx, startY, currentY: mouseMoveEvent.posy, altKey };
E
Erich Gamma 已提交
269

I
isidor 已提交
270
			this._onDidChange.fire(event);
271 272 273
		};

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

J
Joao Moreno 已提交
276
			this.el.removeChild(style);
277

278
			removeClass(this.el, 'active');
I
isidor 已提交
279
			this._onDidEnd.fire();
E
Erich Gamma 已提交
280

281
			dispose(disposables);
282

283 284 285
			const iframes = getElementsByTagName('iframe');
			for (const iframe of iframes) {
				iframe.style.pointerEvents = 'auto';
286
			}
287 288 289 290 291 292 293 294
		};

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

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

B
Benjamin Pasero 已提交
297
	private onTouchStart(event: GestureEvent): void {
298
		EventHelper.stop(event);
E
Erich Gamma 已提交
299

300
		const listeners: IDisposable[] = [];
E
Erich Gamma 已提交
301

302 303
		const startX = event.pageX;
		const startY = event.pageY;
304 305
		const altKey = event.altKey;

I
isidor 已提交
306
		this._onDidStart.fire({
E
Erich Gamma 已提交
307 308 309
			startX: startX,
			currentX: startX,
			startY: startY,
310 311
			currentY: startY,
			altKey
E
Erich Gamma 已提交
312 313
		});

314
		listeners.push(addDisposableListener(this.el, EventType.Change, (event: GestureEvent) => {
B
Benjamin Pasero 已提交
315
			if (types.isNumber(event.pageX) && types.isNumber(event.pageY)) {
I
isidor 已提交
316
				this._onDidChange.fire({
E
Erich Gamma 已提交
317 318 319
					startX: startX,
					currentX: event.pageX,
					startY: startY,
320 321
					currentY: event.pageY,
					altKey
E
Erich Gamma 已提交
322 323 324 325
				});
			}
		}));

326
		listeners.push(addDisposableListener(this.el, EventType.End, (event: GestureEvent) => {
I
isidor 已提交
327
			this._onDidEnd.fire();
J
Joao Moreno 已提交
328
			dispose(listeners);
E
Erich Gamma 已提交
329 330 331
		}));
	}

332 333
	layout(): void {
		const size = isIPad ? 20 : 4;
J
Joao Moreno 已提交
334

E
Erich Gamma 已提交
335
		if (this.orientation === Orientation.VERTICAL) {
336
			const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
337
			this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (size / 2) + 'px';
E
Erich Gamma 已提交
338 339

			if (verticalProvider.getVerticalSashTop) {
340
				this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
E
Erich Gamma 已提交
341 342 343
			}

			if (verticalProvider.getVerticalSashHeight) {
344
				this.el.style.height = verticalProvider.getVerticalSashHeight(this) + 'px';
E
Erich Gamma 已提交
345 346
			}
		} else {
347
			const horizontalProvider = (<IHorizontalSashLayoutProvider>this.layoutProvider);
348
			this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (size / 2) + 'px';
E
Erich Gamma 已提交
349 350

			if (horizontalProvider.getHorizontalSashLeft) {
351
				this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
E
Erich Gamma 已提交
352 353 354
			}

			if (horizontalProvider.getHorizontalSashWidth) {
355
				this.el.style.width = horizontalProvider.getHorizontalSashWidth(this) + 'px';
E
Erich Gamma 已提交
356 357 358 359
			}
		}
	}

360
	show(): void {
E
Erich Gamma 已提交
361
		this.hidden = false;
362 363
		this.el.style.removeProperty('display');
		this.el.setAttribute('aria-hidden', 'false');
E
Erich Gamma 已提交
364 365
	}

366
	hide(): void {
E
Erich Gamma 已提交
367
		this.hidden = true;
368 369
		this.el.style.display = 'none';
		this.el.setAttribute('aria-hidden', 'true');
E
Erich Gamma 已提交
370 371
	}

372
	isHidden(): boolean {
E
Erich Gamma 已提交
373 374 375
		return this.hidden;
	}

376 377
	private onOrthogonalStartSashEnablementChange(state: SashState): void {
		toggleClass(this.el, 'orthogonal-start', state !== SashState.Disabled);
J
Joao Moreno 已提交
378 379
	}

380 381
	private onOrthogonalEndSashEnablementChange(state: SashState): void {
		toggleClass(this.el, 'orthogonal-end', state !== SashState.Disabled);
J
Joao Moreno 已提交
382 383
	}

384
	dispose(): void {
385 386
		super.dispose();

387 388
		this.orthogonalStartSashDisposables = dispose(this.orthogonalStartSashDisposables);
		this.orthogonalEndSashDisposables = dispose(this.orthogonalEndSashDisposables);
J
Joao Moreno 已提交
389

390 391
		if (this.el && this.el.parentElement) {
			this.el.parentElement.removeChild(this.el);
E
Erich Gamma 已提交
392
		}
393 394

		this.el = null;
E
Erich Gamma 已提交
395 396
	}
}