mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-26 10:16:01 +01:00
touch: simplify and only use one Gesture which has global listeners
This commit is contained in:
+51
-123
@@ -7,6 +7,7 @@
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import DomUtils = require('vs/base/browser/dom');
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
export namespace EventType {
|
||||
export const Tap = '-monaco-gesturetap';
|
||||
@@ -63,56 +64,52 @@ interface TouchEvent extends Event {
|
||||
changedTouches: TouchList;
|
||||
}
|
||||
|
||||
export const IS_TOUCH_DEVICE = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
|
||||
const HOLD_DELAY = 700;
|
||||
|
||||
function newGestureEvent(type: string): GestureEvent {
|
||||
let event = <GestureEvent>(<any>document.createEvent('CustomEvent'));
|
||||
event.initEvent(type, false, true);
|
||||
return event;
|
||||
}
|
||||
|
||||
export class Gesture implements IDisposable {
|
||||
|
||||
private static readonly SCROLL_FRICTION = -0.005;
|
||||
private static INSTANCE: Gesture;
|
||||
private static HOLD_DELAY = 700;
|
||||
|
||||
private targetElement: HTMLElement;
|
||||
private callOnTarget: IDisposable[];
|
||||
private targets: HTMLElement[];
|
||||
private toDispose: IDisposable[];
|
||||
private handle: IDisposable;
|
||||
|
||||
private activeTouches: { [id: number]: TouchData; };
|
||||
|
||||
constructor(target: HTMLElement) {
|
||||
this.callOnTarget = [];
|
||||
private constructor() {
|
||||
this.toDispose = [];
|
||||
this.activeTouches = {};
|
||||
this.target = target;
|
||||
this.handle = null;
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchstart', (e) => this.onTouchStart(e)));
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchend', (e) => this.onTouchEnd(e)));
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchmove', (e) => this.onTouchMove(e)));
|
||||
}
|
||||
|
||||
public static addTarget(element: HTMLElement): void {
|
||||
if (!Gesture.isTouchDevice()) {
|
||||
return;
|
||||
}
|
||||
if (!Gesture.INSTANCE) {
|
||||
Gesture.INSTANCE = new Gesture();
|
||||
}
|
||||
|
||||
Gesture.INSTANCE.targets.push(element);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private static isTouchDevice(): boolean {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.target = null;
|
||||
if (this.handle) {
|
||||
this.handle.dispose();
|
||||
dispose(this.toDispose);
|
||||
this.handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
public set target(element: HTMLElement) {
|
||||
this.callOnTarget = dispose(this.callOnTarget);
|
||||
|
||||
this.activeTouches = {};
|
||||
|
||||
this.targetElement = element;
|
||||
|
||||
if (!this.targetElement || !IS_TOUCH_DEVICE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.callOnTarget.push(DomUtils.addDisposableListener(this.targetElement, 'touchstart', (e) => this.onTouchStart(e)));
|
||||
this.callOnTarget.push(DomUtils.addDisposableListener(this.targetElement, 'touchend', (e) => this.onTouchEnd(e)));
|
||||
this.callOnTarget.push(DomUtils.addDisposableListener(this.targetElement, 'touchmove', (e) => this.onTouchMove(e)));
|
||||
}
|
||||
|
||||
private onTouchStart(e: TouchEvent): void {
|
||||
let timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based.
|
||||
e.preventDefault();
|
||||
@@ -136,10 +133,10 @@ export class Gesture implements IDisposable {
|
||||
rollingPageY: [touch.pageY]
|
||||
};
|
||||
|
||||
let evt = newGestureEvent(EventType.Start);
|
||||
let evt = this.newGestureEvent(EventType.Start);
|
||||
evt.pageX = touch.pageX;
|
||||
evt.pageY = touch.pageY;
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,25 +159,25 @@ export class Gesture implements IDisposable {
|
||||
let data = this.activeTouches[touch.identifier],
|
||||
holdTime = Date.now() - data.initialTimeStamp;
|
||||
|
||||
if (holdTime < HOLD_DELAY
|
||||
if (holdTime < Gesture.HOLD_DELAY
|
||||
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
|
||||
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
|
||||
|
||||
let evt = newGestureEvent(EventType.Tap);
|
||||
let evt = this.newGestureEvent(EventType.Tap);
|
||||
evt.initialTarget = data.initialTarget;
|
||||
evt.pageX = arrays.tail(data.rollingPageX);
|
||||
evt.pageY = arrays.tail(data.rollingPageY);
|
||||
this.dispatch(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
} else if (holdTime >= HOLD_DELAY
|
||||
} else if (holdTime >= Gesture.HOLD_DELAY
|
||||
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
|
||||
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
|
||||
|
||||
let evt = newGestureEvent(EventType.Contextmenu);
|
||||
let evt = this.newGestureEvent(EventType.Contextmenu);
|
||||
evt.initialTarget = data.initialTarget;
|
||||
evt.pageX = arrays.tail(data.rollingPageX);
|
||||
evt.pageY = arrays.tail(data.rollingPageY);
|
||||
this.dispatch(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
} else if (activeTouchCount === 1) {
|
||||
let finalX = arrays.tail(data.rollingPageX);
|
||||
@@ -205,8 +202,18 @@ export class Gesture implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected dispatch(event: GestureEvent): void {
|
||||
this.targetElement.dispatchEvent(event);
|
||||
private newGestureEvent(type: string): GestureEvent {
|
||||
let event = <GestureEvent>(<any>document.createEvent('CustomEvent'));
|
||||
event.initEvent(type, false, true);
|
||||
return event;
|
||||
}
|
||||
|
||||
private dispatchEvent(event: GestureEvent): void {
|
||||
this.targets.forEach(target => {
|
||||
if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private inertia(t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void {
|
||||
@@ -232,10 +239,10 @@ export class Gesture implements IDisposable {
|
||||
}
|
||||
|
||||
// dispatch translation event
|
||||
let evt = newGestureEvent(EventType.Change);
|
||||
let evt = this.newGestureEvent(EventType.Change);
|
||||
evt.translationX = delta_pos_x;
|
||||
evt.translationY = delta_pos_y;
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
if (!stopped) {
|
||||
this.inertia(now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y);
|
||||
@@ -259,12 +266,12 @@ export class Gesture implements IDisposable {
|
||||
|
||||
let data = this.activeTouches[touch.identifier];
|
||||
|
||||
let evt = newGestureEvent(EventType.Change);
|
||||
let evt = this.newGestureEvent(EventType.Change);
|
||||
evt.translationX = touch.pageX - arrays.tail(data.rollingPageX);
|
||||
evt.translationY = touch.pageY - arrays.tail(data.rollingPageY);
|
||||
evt.pageX = touch.pageX;
|
||||
evt.pageY = touch.pageY;
|
||||
this.targetElement.dispatchEvent(evt);
|
||||
this.dispatchEvent(evt);
|
||||
|
||||
// only keep a few data points, to average the final speed
|
||||
if (data.rollingPageX.length > 3) {
|
||||
@@ -279,82 +286,3 @@ export class Gesture implements IDisposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SimpleGesture implements IDisposable {
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
private static INSTANCE: SimpleGesture;
|
||||
private targets: HTMLElement[];
|
||||
|
||||
private activeTouches: { [id: number]: TouchData; };
|
||||
|
||||
private constructor() {
|
||||
this.toDispose = [];
|
||||
this.activeTouches = {};
|
||||
this.targets = [];
|
||||
this.toDispose.push(DomUtils.addDisposableListener(document, 'touchend', (e) => this.onTouchEnd(e)));
|
||||
}
|
||||
|
||||
public static addTarget(element: HTMLElement): void {
|
||||
if (!SimpleGesture.INSTANCE) {
|
||||
SimpleGesture.INSTANCE = new SimpleGesture();
|
||||
}
|
||||
|
||||
if (IS_TOUCH_DEVICE) {
|
||||
SimpleGesture.INSTANCE.targets.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(this.toDispose);
|
||||
}
|
||||
|
||||
private onTouchEnd(e: TouchEvent): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
for (let i = 0, len = e.changedTouches.length; i < len; i++) {
|
||||
|
||||
let touch = e.changedTouches.item(i);
|
||||
|
||||
if (!this.activeTouches.hasOwnProperty(String(touch.identifier))) {
|
||||
console.warn('move of an UNKNOWN touch', touch);
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = this.activeTouches[touch.identifier],
|
||||
holdTime = Date.now() - data.initialTimeStamp;
|
||||
|
||||
if (holdTime < HOLD_DELAY
|
||||
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
|
||||
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
|
||||
|
||||
let evt = newGestureEvent(EventType.Tap);
|
||||
evt.initialTarget = data.initialTarget;
|
||||
evt.pageX = arrays.tail(data.rollingPageX);
|
||||
evt.pageY = arrays.tail(data.rollingPageY);
|
||||
this.dispatch(evt);
|
||||
} else if (holdTime >= HOLD_DELAY
|
||||
&& Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)) < 30
|
||||
&& Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)) < 30) {
|
||||
|
||||
let evt = newGestureEvent(EventType.Contextmenu);
|
||||
evt.initialTarget = data.initialTarget;
|
||||
evt.pageX = arrays.tail(data.rollingPageX);
|
||||
evt.pageY = arrays.tail(data.rollingPageY);
|
||||
this.dispatch(evt);
|
||||
}
|
||||
|
||||
// forget about this touch
|
||||
delete this.activeTouches[touch.identifier];
|
||||
}
|
||||
}
|
||||
|
||||
protected dispatch(event: GestureEvent): void {
|
||||
this.targets.forEach(target => {
|
||||
if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) {
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import types = require('vs/base/common/types');
|
||||
import { EventType, SimpleGesture } from 'vs/base/browser/touch';
|
||||
import { EventType, Gesture } from 'vs/base/browser/touch';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
@@ -105,7 +105,7 @@ export class BaseActionItem implements IActionItem {
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.builder = $(container);
|
||||
SimpleGesture.addTarget(container);
|
||||
Gesture.addTarget(container);
|
||||
|
||||
const enableDragging = this.options && this.options.draggable;
|
||||
if (enableDragging) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import 'vs/css!./dropdown';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { SimpleGesture, EventType as GestureEventType } from 'vs/base/browser/touch';
|
||||
import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch';
|
||||
import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
@@ -67,7 +67,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
this._toDispose.push(cleanupFn);
|
||||
}
|
||||
|
||||
SimpleGesture.addTarget(this.$label.getHTMLElement());
|
||||
Gesture.addTarget(this.$label.getHTMLElement());
|
||||
}
|
||||
|
||||
public get toDispose(): IDisposable[] {
|
||||
|
||||
@@ -106,7 +106,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
this.rowsContainer = document.createElement('div');
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
this.gesture = new Gesture(this.rowsContainer);
|
||||
Gesture.addTarget(this.rowsContainer);
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
|
||||
@@ -378,7 +378,6 @@ export class TreeView extends HeightMap {
|
||||
private styleElement: HTMLStyleElement;
|
||||
private rowsContainer: HTMLElement;
|
||||
private scrollableElement: ScrollableElement;
|
||||
private wrapperGesture: Touch.Gesture;
|
||||
private msGesture: MSGesture;
|
||||
private lastPointerType: string;
|
||||
private lastClickTimeStamp: number = 0;
|
||||
@@ -475,7 +474,7 @@ export class TreeView extends HeightMap {
|
||||
this.wrapper.style.msTouchAction = 'none';
|
||||
this.wrapper.style.msContentZooming = 'none';
|
||||
} else {
|
||||
this.wrapperGesture = new Touch.Gesture(this.wrapper);
|
||||
Touch.Gesture.addTarget(this.wrapper);
|
||||
}
|
||||
|
||||
this.rowsContainer = document.createElement('div');
|
||||
@@ -1642,11 +1641,6 @@ export class TreeView extends HeightMap {
|
||||
}
|
||||
this.domNode = null;
|
||||
|
||||
if (this.wrapperGesture) {
|
||||
this.wrapperGesture.dispose();
|
||||
this.wrapperGesture = null;
|
||||
}
|
||||
|
||||
if (this.context.cache) {
|
||||
this.context.cache.dispose();
|
||||
this.context.cache = null;
|
||||
|
||||
@@ -188,12 +188,10 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
|
||||
|
||||
class TouchHandler extends MouseHandler {
|
||||
|
||||
private gesture: Gesture;
|
||||
|
||||
constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
|
||||
super(context, viewController, viewHelper);
|
||||
|
||||
this.gesture = new Gesture(this.viewHelper.linesContentDomNode);
|
||||
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)));
|
||||
@@ -202,7 +200,6 @@ class TouchHandler extends MouseHandler {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.gesture.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { EventType as TouchEventType, GestureEvent, SimpleGesture } from 'vs/base/browser/touch';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
|
||||
export class NoTabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
@@ -31,7 +31,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.titleContainer = parent;
|
||||
|
||||
// Gesture Support
|
||||
SimpleGesture.addTarget(this.titleContainer);
|
||||
Gesture.addTarget(this.titleContainer);
|
||||
|
||||
// Pin on double click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
@@ -519,7 +519,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
DOM.addClass(tabContainer, 'tab');
|
||||
|
||||
// Gesture Support
|
||||
const gestureSupport = new Gesture(tabContainer);
|
||||
Gesture.addTarget(tabContainer);
|
||||
|
||||
// Tab Editor Label
|
||||
const editorLabel = this.instantiationService.createInstance(ResourceLabel, tabContainer, void 0);
|
||||
@@ -536,7 +536,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Eventing
|
||||
const disposable = this.hookTabListeners(tabContainer, index);
|
||||
|
||||
this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel, gestureSupport]));
|
||||
this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel]));
|
||||
|
||||
return tabContainer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user