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

A
Alex Dima 已提交
6
import * as dom from 'vs/base/browser/dom';
7
import * as platform from 'vs/base/common/platform';
J
Johannes Rieken 已提交
8
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
M
Matt Bierner 已提交
9
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
10
import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler';
A
Alex Dima 已提交
11
import { IMouseTarget } from 'vs/editor/browser/editorBrowser';
12
import { EditorMouseEvent, EditorPointerEventFactory } from 'vs/editor/browser/editorDom';
13
import { ViewController } from 'vs/editor/browser/view/viewController';
A
Alex Dima 已提交
14
import { ViewContext } from 'vs/editor/common/view/viewContext';
15
import { BrowserFeatures } from 'vs/base/browser/canIUse';
E
Erich Gamma 已提交
16 17 18 19 20 21

interface IThrottledGestureEvent {
	translationX: number;
	translationY: number;
}

A
Alex Dima 已提交
22
function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent | null, currentEvent: MSGestureEvent): IThrottledGestureEvent {
M
Matt Bierner 已提交
23
	const r = {
E
Erich Gamma 已提交
24 25 26 27 28 29 30 31
		translationY: currentEvent.translationY,
		translationX: currentEvent.translationX
	};
	if (lastEvent) {
		r.translationY += lastEvent.translationY;
		r.translationX += lastEvent.translationX;
	}
	return r;
32
}
E
Erich Gamma 已提交
33 34 35 36

/**
 * Basically Edge but should be modified to handle any pointerEnabled, even without support of MSGesture
 */
A
Alex Dima 已提交
37
class StandardPointerHandler extends MouseHandler implements IDisposable {
E
Erich Gamma 已提交
38 39 40 41

	private _lastPointerType: string;
	private _installGestureHandlerTimeout: number;

A
Alex Dima 已提交
42
	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
E
Erich Gamma 已提交
43 44 45 46 47 48 49 50 51 52
		super(context, viewController, viewHelper);

		this.viewHelper.linesContentDomNode.style.touchAction = 'none';

		// TODO@Alex -> this expects that the view is added in 100 ms, might not be the case
		// This handler should be added when the dom node is in the dom tree
		this._installGestureHandlerTimeout = window.setTimeout(() => {
			this._installGestureHandlerTimeout = -1;

			// TODO@Alex: replace the usage of MSGesture here with something that works across all browsers
J
Johannes Rieken 已提交
53
			if ((<any>window).MSGesture) {
M
Matt Bierner 已提交
54 55
				const touchGesture = new MSGesture();
				const penGesture = new MSGesture();
E
Erich Gamma 已提交
56 57
				touchGesture.target = this.viewHelper.linesContentDomNode;
				penGesture.target = this.viewHelper.linesContentDomNode;
A
Alex Dima 已提交
58
				this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: PointerEvent) => {
M
Matt Bierner 已提交
59
					const pointerType = <any>e.pointerType;
E
Erich Gamma 已提交
60 61 62 63 64 65 66 67 68 69 70
					if (pointerType === 'mouse') {
						this._lastPointerType = 'mouse';
						return;
					} else if (pointerType === 'touch') {
						this._lastPointerType = 'touch';
						touchGesture.addPointer(e.pointerId);
					} else {
						this._lastPointerType = 'pen';
						penGesture.addPointer(e.pointerId);
					}
				});
A
Alex Dima 已提交
71
				this._register(dom.addDisposableThrottledListener<IThrottledGestureEvent, MSGestureEvent>(this.viewHelper.linesContentDomNode, 'MSGestureChange', (e) => this._onGestureChange(e), gestureChangeEventMerger));
A
Alex Dima 已提交
72
				this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'MSGestureTap', (e) => this._onCaptureGestureTap(e), true));
E
Erich Gamma 已提交
73 74 75 76 77
			}
		}, 100);
		this._lastPointerType = 'mouse';
	}

J
Johannes Rieken 已提交
78
	public _onMouseDown(e: EditorMouseEvent): void {
E
Erich Gamma 已提交
79 80 81 82 83 84
		if (this._lastPointerType === 'mouse') {
			super._onMouseDown(e);
		}
	}

	private _onCaptureGestureTap(rawEvent: MSGestureEvent): void {
M
Matt Bierner 已提交
85 86
		const e = new EditorMouseEvent(<MouseEvent><any>rawEvent, this.viewHelper.viewDomNode);
		const t = this._createMouseTarget(e, false);
E
Erich Gamma 已提交
87
		if (t.position) {
88
			this.viewController.moveTo(t.position);
E
Erich Gamma 已提交
89 90 91 92 93 94 95 96 97 98 99 100 101
		}
		// IE does not want to focus when coming in from the browser's address bar
		if ((<any>e.browserEvent).fromElement) {
			e.preventDefault();
			this.viewHelper.focusTextArea();
		} else {
			// TODO@Alex -> cancel this is focus is lost
			setTimeout(() => {
				this.viewHelper.focusTextArea();
			});
		}
	}

J
Johannes Rieken 已提交
102
	private _onGestureChange(e: IThrottledGestureEvent): void {
103
		this._context.model.deltaScrollNow(-e.translationX, -e.translationY);
E
Erich Gamma 已提交
104 105 106 107 108 109 110 111
	}

	public dispose(): void {
		window.clearTimeout(this._installGestureHandlerTimeout);
		super.dispose();
	}
}

112 113 114 115 116 117 118 119 120 121 122
/**
 * Currently only tested on iOS 13/ iPadOS.
 */
export class PointerEventHandler extends MouseHandler {
	private _lastPointerType: string;
	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
		super(context, viewController, viewHelper);

		this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode));
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e)));
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e)));
123
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e: MouseEvent) => this._onContextMenu(new EditorMouseEvent(e, this.viewHelper.viewDomNode), false)));
124 125 126

		this._lastPointerType = 'mouse';

P
Peng Lyu 已提交
127
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, 'pointerdown', (e: any) => {
128 129 130 131 132 133 134 135 136
			const pointerType = <any>e.pointerType;
			if (pointerType === 'mouse') {
				this._lastPointerType = 'mouse';
				return;
			} else if (pointerType === 'touch') {
				this._lastPointerType = 'touch';
			} else {
				this._lastPointerType = 'pen';
			}
P
Peng Lyu 已提交
137
		}));
138 139 140 141 142 143 144 145 146 147 148 149 150

		// PonterEvents
		const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode);

		this._register(pointerEvents.onPointerMoveThrottled(this.viewHelper.viewDomNode,
			(e) => this._onMouseMove(e),
			createMouseMoveEventMerger(this.mouseTargetFactory), MouseHandler.MOUSE_MOVE_MINIMUM_TIME));
		this._register(pointerEvents.onPointerUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e)));
		this._register(pointerEvents.onPointerLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e)));
		this._register(pointerEvents.onPointerDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e)));
	}

	private onTap(event: GestureEvent): void {
P
Peng Lyu 已提交
151 152 153 154
		if (!event.initialTarget || !this.viewHelper.linesContentDomNode.contains(<any>event.initialTarget)) {
			return;
		}

155 156 157 158 159
		event.preventDefault();
		this.viewHelper.focusTextArea();
		const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false);

		if (target.position) {
P
Peng Lyu 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
			// this.viewController.moveTo(target.position);
			this.viewController.dispatchMouse({
				position: target.position,
				mouseColumn: target.position.column,
				startedOnLineNumbers: false,
				mouseDownCount: event.tapCount,
				inSelectionMode: false,
				altKey: false,
				ctrlKey: false,
				metaKey: false,
				shiftKey: false,

				leftButton: false,
				middleButton: false,
			});
175 176 177 178 179
		}
	}

	private onChange(e: GestureEvent): void {
		if (this._lastPointerType === 'touch') {
180
			this._context.model.deltaScrollNow(-e.translationX, -e.translationY);
181 182 183 184
		}
	}

	public _onMouseDown(e: EditorMouseEvent): void {
P
Peng Lyu 已提交
185 186
		if (e.target && this.viewHelper.linesContentDomNode.contains(e.target) && this._lastPointerType === 'touch') {
			return;
187
		}
P
Peng Lyu 已提交
188 189

		super._onMouseDown(e);
190 191 192
	}
}

A
Alex Dima 已提交
193
class TouchHandler extends MouseHandler {
E
Erich Gamma 已提交
194

A
Alex Dima 已提交
195
	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
E
Erich Gamma 已提交
196 197
		super(context, viewController, viewHelper);

198
		this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode));
E
Erich Gamma 已提交
199

A
Alex Dima 已提交
200 201 202
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e)));
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e)));
		this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e: MouseEvent) => this._onContextMenu(new EditorMouseEvent(e, this.viewHelper.viewDomNode), false)));
E
Erich Gamma 已提交
203 204
	}

J
Johannes Rieken 已提交
205
	private onTap(event: GestureEvent): void {
E
Erich Gamma 已提交
206 207 208 209
		event.preventDefault();

		this.viewHelper.focusTextArea();

M
Matt Bierner 已提交
210
		const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false);
E
Erich Gamma 已提交
211 212

		if (target.position) {
213
			this.viewController.moveTo(target.position);
E
Erich Gamma 已提交
214 215 216
		}
	}

J
Johannes Rieken 已提交
217
	private onChange(e: GestureEvent): void {
218
		this._context.model.deltaScrollNow(-e.translationX, -e.translationY);
E
Erich Gamma 已提交
219 220 221
	}
}

M
Matt Bierner 已提交
222
export class PointerHandler extends Disposable {
223
	private readonly handler: MouseHandler;
E
Erich Gamma 已提交
224

A
Alex Dima 已提交
225
	constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
M
Matt Bierner 已提交
226
		super();
A
Alex Dima 已提交
227
		if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
228
			this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
J
Johannes Rieken 已提交
229
		} else if ((<any>window).TouchEvent) {
M
Matt Bierner 已提交
230
			this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
231
		} else if (window.navigator.pointerEnabled || (<any>window).PointerEvent) {
M
Matt Bierner 已提交
232
			this.handler = this._register(new StandardPointerHandler(context, viewController, viewHelper));
E
Erich Gamma 已提交
233
		} else {
M
Matt Bierner 已提交
234
			this.handler = this._register(new MouseHandler(context, viewController, viewHelper));
E
Erich Gamma 已提交
235 236 237
		}
	}

A
Alex Dima 已提交
238
	public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {
239
		return this.handler.getTargetAtClientPoint(clientX, clientY);
E
Erich Gamma 已提交
240 241
	}
}