sash.ts 9.9 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 10
import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle';
import { Builder, $, Dimension } from 'vs/base/browser/builder';
J
Johannes Rieken 已提交
11 12
import { isIPad } from 'vs/base/browser/browser';
import { isMacintosh } from 'vs/base/common/platform';
13 14
import types = require('vs/base/common/types');
import DOM = require('vs/base/browser/dom');
15
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
J
Johannes Rieken 已提交
16
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
M
Matt Bierner 已提交
17
import { Event, Emitter } from 'vs/base/common/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

B
Benjamin Pasero 已提交
53
	private $e: Builder;
E
Erich Gamma 已提交
54 55 56 57 58 59
	private layoutProvider: ISashLayoutProvider;
	private isDisabled: boolean;
	private hidden: boolean;
	private orientation: Orientation;
	private size: number;

I
isidor 已提交
60 61 62 63 64
	private _onDidStart = new Emitter<ISashEvent>();
	private _onDidChange = new Emitter<ISashEvent>();
	private _onDidReset = new Emitter<void>();
	private _onDidEnd = new Emitter<void>();

E
Erich Gamma 已提交
65 66 67 68
	constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions = {}) {

		this.$e = $('.monaco-sash').appendTo(container);

B
Benjamin Pasero 已提交
69 70 71 72
		if (isMacintosh) {
			this.$e.addClass('mac');
		}

73
		this.$e.on(DOM.EventType.MOUSE_DOWN, (e) => { this.onMouseDown(e as MouseEvent); });
I
isidor 已提交
74
		this.$e.on(DOM.EventType.DBLCLICK, (e) => this._onDidReset.fire());
75
		Gesture.addTarget(this.$e.getHTMLElement());
76
		this.$e.on(EventType.Start, (e) => { this.onTouchStart(e as GestureEvent); });
E
Erich Gamma 已提交
77 78 79

		this.size = options.baseSize || 5;

B
Benjamin Pasero 已提交
80
		if (isIPad) {
E
Erich Gamma 已提交
81 82 83 84
			this.size *= 4; // see also http://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
			this.$e.addClass('touch');
		}

B
Benjamin Pasero 已提交
85
		this.setOrientation(options.orientation || Orientation.VERTICAL);
E
Erich Gamma 已提交
86 87 88 89 90 91

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

I
isidor 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	public get onDidStart(): Event<ISashEvent> {
		return this._onDidStart.event;
	}

	public get onDidChange(): Event<ISashEvent> {
		return this._onDidChange.event;
	}

	public get onDidReset(): Event<void> {
		return this._onDidReset.event;
	}

	public get onDidEnd(): Event<void> {
		return this._onDidEnd.event;
	}

B
Benjamin Pasero 已提交
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
	public setOrientation(orientation: Orientation): void {
		this.orientation = orientation;

		this.$e.removeClass('horizontal', 'vertical');
		this.$e.addClass(this.getOrientation());

		if (this.orientation === Orientation.HORIZONTAL) {
			this.$e.size(null, this.size);
		} else {
			this.$e.size(this.size);
		}

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

M
Maxime Quandalle 已提交
125 126 127 128
	private getOrientation(): 'horizontal' | 'vertical' {
		return this.orientation === Orientation.HORIZONTAL ? 'horizontal' : 'vertical';
	}

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

		if (this.isDisabled) {
			return;
		}

136 137 138 139
		const iframes = $(DOM.getElementsByTagName('iframe'));
		if (iframes) {
			iframes.style('pointer-events', 'none'); // disable mouse events on iframes as long as we drag the sash
		}
140

B
Benjamin Pasero 已提交
141 142 143
		let mouseDownEvent = new StandardMouseEvent(e);
		let startX = mouseDownEvent.posx;
		let startY = mouseDownEvent.posy;
144
		const altKey = mouseDownEvent.altKey;
E
Erich Gamma 已提交
145

B
Benjamin Pasero 已提交
146
		let startEvent: ISashEvent = {
E
Erich Gamma 已提交
147 148 149
			startX: startX,
			currentX: startX,
			startY: startY,
150 151
			currentY: startY,
			altKey
E
Erich Gamma 已提交
152 153 154
		};

		this.$e.addClass('active');
I
isidor 已提交
155
		this._onDidStart.fire(startEvent);
E
Erich Gamma 已提交
156

B
Benjamin Pasero 已提交
157
		let $window = $(window);
Y
Yuki Ueda 已提交
158
		let containerCSSClass = `${this.getOrientation()}-cursor-container${isMacintosh ? '-mac' : ''}`;
E
Erich Gamma 已提交
159

160
		$window.on('mousemove', (e) => {
E
Erich Gamma 已提交
161
			DOM.EventHelper.stop(e, false);
162
			let mouseMoveEvent = new StandardMouseEvent(e as MouseEvent);
E
Erich Gamma 已提交
163

B
Benjamin Pasero 已提交
164
			let event: ISashEvent = {
E
Erich Gamma 已提交
165 166 167
				startX: startX,
				currentX: mouseMoveEvent.posx,
				startY: startY,
168 169
				currentY: mouseMoveEvent.posy,
				altKey
E
Erich Gamma 已提交
170 171
			};

I
isidor 已提交
172
			this._onDidChange.fire(event);
173
		}).once('mouseup', (e) => {
E
Erich Gamma 已提交
174 175
			DOM.EventHelper.stop(e, false);
			this.$e.removeClass('active');
I
isidor 已提交
176
			this._onDidEnd.fire();
E
Erich Gamma 已提交
177 178

			$window.off('mousemove');
Y
Yuki Ueda 已提交
179
			document.body.classList.remove(containerCSSClass);
180

181 182 183 184
			const iframes = $(DOM.getElementsByTagName('iframe'));
			if (iframes) {
				iframes.style('pointer-events', 'auto');
			}
E
Erich Gamma 已提交
185 186
		});

Y
Yuki Ueda 已提交
187
		document.body.classList.add(containerCSSClass);
E
Erich Gamma 已提交
188 189
	}

B
Benjamin Pasero 已提交
190
	private onTouchStart(event: GestureEvent): void {
E
Erich Gamma 已提交
191 192
		DOM.EventHelper.stop(event);

B
Benjamin Pasero 已提交
193
		let listeners: IDisposable[] = [];
E
Erich Gamma 已提交
194

B
Benjamin Pasero 已提交
195 196
		let startX = event.pageX;
		let startY = event.pageY;
197 198
		const altKey = event.altKey;

E
Erich Gamma 已提交
199

I
isidor 已提交
200
		this._onDidStart.fire({
E
Erich Gamma 已提交
201 202 203
			startX: startX,
			currentX: startX,
			startY: startY,
204 205
			currentY: startY,
			altKey
E
Erich Gamma 已提交
206 207
		});

B
Benjamin Pasero 已提交
208 209
		listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.Change, (event: GestureEvent) => {
			if (types.isNumber(event.pageX) && types.isNumber(event.pageY)) {
I
isidor 已提交
210
				this._onDidChange.fire({
E
Erich Gamma 已提交
211 212 213
					startX: startX,
					currentX: event.pageX,
					startY: startY,
214 215
					currentY: event.pageY,
					altKey
E
Erich Gamma 已提交
216 217 218 219
				});
			}
		}));

B
Benjamin Pasero 已提交
220
		listeners.push(DOM.addDisposableListener(this.$e.getHTMLElement(), EventType.End, (event: GestureEvent) => {
I
isidor 已提交
221
			this._onDidEnd.fire();
J
Joao Moreno 已提交
222
			dispose(listeners);
E
Erich Gamma 已提交
223 224 225 226
		}));
	}

	public layout(): void {
B
Benjamin Pasero 已提交
227
		let style: { top?: string; left?: string; height?: string; width?: string; };
E
Erich Gamma 已提交
228 229

		if (this.orientation === Orientation.VERTICAL) {
B
Benjamin Pasero 已提交
230
			let verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
E
Erich Gamma 已提交
231 232 233 234 235 236 237 238 239 240
			style = { left: verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px' };

			if (verticalProvider.getVerticalSashTop) {
				style.top = verticalProvider.getVerticalSashTop(this) + 'px';
			}

			if (verticalProvider.getVerticalSashHeight) {
				style.height = verticalProvider.getVerticalSashHeight(this) + 'px';
			}
		} else {
B
Benjamin Pasero 已提交
241
			let horizontalProvider = (<IHorizontalSashLayoutProvider>this.layoutProvider);
E
Erich Gamma 已提交
242 243 244
			style = { top: horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px' };

			if (horizontalProvider.getHorizontalSashLeft) {
J
Joao Moreno 已提交
245
				style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
E
Erich Gamma 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
			}

			if (horizontalProvider.getHorizontalSashWidth) {
				style.width = horizontalProvider.getHorizontalSashWidth(this) + 'px';
			}
		}

		this.$e.style(style);
	}

	public show(): void {
		this.hidden = false;
		this.$e.show();
	}

	public hide(): void {
		this.hidden = true;
		this.$e.hide();
	}

	public isHidden(): boolean {
		return this.hidden;
	}

	public enable(): void {
		this.$e.removeClass('disabled');
		this.isDisabled = false;
	}

	public disable(): void {
		this.$e.addClass('disabled');
		this.isDisabled = true;
	}

J
Joao Moreno 已提交
280 281 282 283
	get enabled(): boolean {
		return !this.isDisabled;
	}

E
Erich Gamma 已提交
284 285 286 287 288 289 290
	public dispose(): void {
		if (this.$e) {
			this.$e.destroy();
			this.$e = null;
		}
	}
}
S
Sandeep Somavarapu 已提交
291 292 293 294 295 296 297 298 299 300 301 302 303

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

B
Benjamin Pasero 已提交
307
	constructor(container: HTMLElement, private minWidth: number) {
S
Sandeep Somavarapu 已提交
308 309 310 311
		super();
		this.ratio = 0.5;
		this.sash = new Sash(container, this);

I
isidor 已提交
312 313 314 315
		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 已提交
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
	}

	public getVerticalSashTop(): number {
		return 0;
	}

	public getVerticalSashLeft(): number {
		return this.position;
	}

	public getVerticalSashHeight(): number {
		return this.dimension.height;
	}

	public setDimenesion(dimension: Dimension) {
		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 已提交
354
		this.compute(0.5);
S
Sandeep Somavarapu 已提交
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
		this._onPositionChange.fire(this.position);
		this.sash.layout();
	}

	private computeSashPosition(sashRatio: number = this.ratio) {
		let contentWidth = this.dimension.width;
		let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth);
		let midPoint = Math.floor(0.5 * contentWidth);

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