import * as React from 'react'; import { action } from 'mobx'; import { InteractionUtils } from '../util/InteractionUtils'; const HOLD_DURATION = 1000; export abstract class Touchable extends React.Component> { //private holdTimer: NodeJS.Timeout | undefined; private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; private endDisposer?: InteractionUtils.MultiTouchEventDisposer; private holdMoveDisposer?: InteractionUtils.MultiTouchEventDisposer; private holdEndDisposer?: InteractionUtils.MultiTouchEventDisposer; protected abstract _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _touchDrag: boolean = false; protected prevPoints: Map = new Map(); public FirstX: number = 0; public FirstY: number = 0; public SecondX: number = 0; public SecondY: number = 0; /** * When a touch even starts, we keep track of each touch that is associated with that event */ @action protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen for (const pt of me.touches) { actualPts.push(pt); if (this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); } // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives for (const tPt of me.changedTouches) { if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) { // pen is also a touch, but with a radius of 0.5 (at least with the surface pens) // and this seems to be the only way of differentiating pen and touch on touch events if ((pt as any).radiusX > 1 && (pt as any).radiusY > 1) { this.prevPoints.set(pt.identifier, pt); } } } } const ptsToDelete: number[] = []; this.prevPoints.forEach(pt => { if (!actualPts.includes(pt)) { ptsToDelete.push(pt.identifier); } }); ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); if (this.prevPoints.size) { switch (this.prevPoints.size) { case 1: this.handle1PointerDown(te, me); te.persist(); // -- code for radial menu -- // if (this.holdTimer) { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } break; case 2: this.handle2PointersDown(te, me); break; } } }; /** * Handle touch move event */ @action protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const te = me.touchEvent; const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); // if we're not actually moving a lot, don't consider it as dragging yet if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; switch (myTouches.length) { case 1: this.handle1PointerMove(te, me); break; case 2: this.handle2PointersMove(te, me); break; } for (const pt of me.touches) { if (pt && this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); } } }; @action protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { // remove all the touches associated with the event const te = me.touchEvent; for (const pt of me.changedTouches) { if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.delete(pt.identifier); } } } this._touchDrag = false; te.stopPropagation(); // if (e.targetTouches.length === 0) { // this.prevPoints.clear(); // } if (this.prevPoints.size === 0) { this.cleanUpInteractions(); } e.stopPropagation(); }; cleanUpInteractions = (): void => { this.removeMoveListeners(); this.removeEndListeners(); }; handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); }; handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); }; handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); }; handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); }; handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); me.touchEvent.stopPropagation(); this.removeMoveListeners(); this.removeEndListeners(); this.removeHoldMoveListeners(); this.removeHoldEndListeners(); this.addHoldMoveListeners(); this.addHoldEndListeners(); }; addMoveListeners = () => { const handler = (e: Event) => this.onTouch(e, (e as CustomEvent>).detail); document.addEventListener('dashOnTouchMove', handler); this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler); }; addEndListeners = () => { const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent>).detail); document.addEventListener('dashOnTouchEnd', handler); this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler); }; addHoldMoveListeners = () => { const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent>).detail); document.addEventListener('dashOnTouchHoldMove', handler); this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler); }; addHoldEndListeners = () => { const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent>).detail); document.addEventListener('dashOnTouchHoldEnd', handler); this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler); }; removeMoveListeners = () => this.moveDisposer?.(); removeEndListeners = () => this.endDisposer?.(); removeHoldMoveListeners = () => this.holdMoveDisposer?.(); removeHoldEndListeners = () => this.holdEndDisposer?.(); handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { // e.stopPropagation(); // me.touchEvent.stopPropagation(); }; handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { e.stopPropagation(); me.touchEvent.stopPropagation(); this.removeHoldMoveListeners(); this.removeHoldEndListeners(); me.touchEvent.stopPropagation(); me.touchEvent.preventDefault(); }; handleHandDown = (e: React.TouchEvent) => { // e.stopPropagation(); // e.preventDefault(); }; }