提交 8cc22f3e 编写于 作者: I isidor

touch: simplify and only use one Gesture which has global listeners

上级 3f0147bd
......@@ -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,54 +64,50 @@ 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 dispose(): void {
this.target = null;
if (this.handle) {
this.handle.dispose();
this.handle = null;
public static addTarget(element: HTMLElement): void {
if (!Gesture.isTouchDevice()) {
return;
}
if (!Gesture.INSTANCE) {
Gesture.INSTANCE = new Gesture();
}
}
public set target(element: HTMLElement) {
this.callOnTarget = dispose(this.callOnTarget);
this.activeTouches = {};
Gesture.INSTANCE.targets.push(element);
}
this.targetElement = element;
@memoize
private static isTouchDevice(): boolean {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0;
}
if (!this.targetElement || !IS_TOUCH_DEVICE) {
return;
public dispose(): void {
if (this.handle) {
this.handle.dispose();
dispose(this.toDispose);
this.handle = null;
}
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 {
......@@ -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;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册