diff options
-rw-r--r-- | src/client/util/InteractionUtils.ts | 50 | ||||
-rw-r--r-- | src/client/views/CollectionLinearView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/GestureOverlay.tsx | 160 | ||||
-rw-r--r-- | src/client/views/Touchable.tsx | 114 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 8 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 80 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 32 | ||||
-rw-r--r-- | src/pen-gestures/GestureUtils.ts | 2 |
9 files changed, 344 insertions, 112 deletions
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index 76b43da3c..63ee2fb92 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -8,13 +8,53 @@ export namespace InteractionUtils { const REACT_POINTER_PEN_BUTTON = 0; const ERASER_BUTTON = 5; + export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> { + constructor( + readonly fingers: number, + readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, + readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent + ) { } + } + + export interface MultiTouchEventDisposer { (): void; } + + export function MakeMultiTouchTarget( + element: HTMLElement, + startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void, + ): MultiTouchEventDisposer { + const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail); + // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined; + // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined; + element.addEventListener("dashOnTouchStart", onMultiTouchStartHandler); + // if (onMultiTouchMoveHandler) { + // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler); + // } + // if (onMultiTouchEndHandler) { + // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler); + // } + return () => { + element.removeEventListener("dashOnTouchStart", onMultiTouchStartHandler); + // if (onMultiTouchMoveHandler) { + // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler); + // } + // if (onMultiTouchEndHandler) { + // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler); + // } + }; + } + export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] { const myTouches = new Array<React.Touch>(); - for (let i = 0; i < e.targetTouches.length; i++) { - const pt: any = e.targetTouches.item(i); - if (pt && prevPoints.has(pt.identifier)) { - if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { - myTouches.push(pt); + for (let i = 0; i < e.touches.length; i++) { + const pt: any = e.touches.item(i); + if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { + for (let j = 0; j < e.targetTouches.length; j++) { + const tPt = e.targetTouches.item(j); + if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { + if (pt && prevPoints.has(pt.identifier)) { + myTouches.push(pt); + } + } } } } diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 5d6a58656..38f2decdd 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -36,7 +36,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { { fireImmediately: true } ); } - protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); @@ -55,7 +55,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { render() { const guid = Utils.GenerateGuid(); return <div className="collectionLinearView-outer"> - <div className="collectionLinearView" ref={this.createDropAndGestureTarget} > + <div className="collectionLinearView" ref={this.createDashEventsTarget} > <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle} onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} /> <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label> diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 830c06f1f..a5fba362c 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; -import "./GestureOverlay.scss" +import "./GestureOverlay.scss"; import { computed, observable, action, runInAction } from "mobx"; import { CreatePolyline } from "./InkingStroke"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; @@ -16,6 +16,7 @@ import { Scripting } from "../util/Scripting"; import { FieldValue, Cast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import Palette from "./Palette"; +import { Utils } from "../../Utils"; @observer export default class GestureOverlay extends Touchable { @@ -28,6 +29,9 @@ export default class GestureOverlay extends Touchable { private _d1: Doc | undefined; private thumbIdentifier?: number; + private _hands: (React.Touch[])[] = []; + + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; constructor(props: Readonly<{}>) { super(props); @@ -35,11 +39,143 @@ export default class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } - @action + getNewTouches(e: React.TouchEvent | TouchEvent) { + const ntt: (React.Touch | Touch)[] = []; + const nct: (React.Touch | Touch)[] = []; + const nt: (React.Touch | Touch)[] = []; + this._hands.forEach((hand) => { + for (let i = 0; i < e.targetTouches.length; i++) { + const pt = e.targetTouches.item(i); + if (pt && !hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + ntt.push(pt); + } + } + + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.changedTouches.item(i); + if (pt && !hand.some((finger: React.Touch) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + nct.push(pt); + } + } + + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.touches.item(i); + if (pt && !hand.some((finger: React.Touch) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + nt.push(pt); + } + } + }); + return { ntt, nct, nt }; + } + + onReactTouchStart = (te: React.TouchEvent) => { + const actualPts: React.Touch[] = []; + for (let i = 0; i < te.touches.length; i++) { + const pt: any = te.touches.item(i); + actualPts.push(pt); + // 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.radiusX > 1 && pt.radiusY > 1) { + // if (typeof pt.identifier !== "string") { + // pt.identifier = Utils.GenerateGuid(); + // } + 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 && this.prevPoints.size < 5) { + const nts: any = this.getNewTouches(te); + const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + target?.dispatchEvent( + new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchStart", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + points: te.targetTouches, + touchEvent: new React.TouchEvent("start", { + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct + }); + } + } + ) + ); + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.addEventListener("touchmove", this.onReactTouchMove); + document.addEventListener("touchend", this.onReactTouchEnd); + } + else { + this.handleHandDown(te); + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + } + } + + onReactTouchMove = (e: TouchEvent) => { + const nts: any = this.getNewTouches(e); + document.dispatchEvent( + new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchMove", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + points: e.touches, + touchEvent: new TouchEvent("move", { + targetTouches: nts.ntt, + changedTouches: nts.nct, + touches: nts.nt, + }) + } + }) + ); + } + + onReactTouchEnd = (e: TouchEvent) => { + document.dispatchEvent( + new CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>("dashOnTouchEnd", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + points: e.touches, + touchEvent: e + } + }) + ); + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.changedTouches.item(i); + if (pt) { + if (this.prevPoints.has(pt.identifier)) { + this.prevPoints.delete(pt.identifier); + } + } + } + + if (this.prevPoints.size === 0) { + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + } + e.stopPropagation(); + } + handleHandDown = (e: React.TouchEvent) => { const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); this.thumbIdentifier = thumb?.identifier; + fingers.forEach((f) => this.prevPoints.delete(f.identifier)); + this._hands.push(fingers); const others = fingers.filter(f => f !== thumb); const minX = Math.min(...others.map(f => f.clientX)); const minY = Math.min(...others.map(f => f.clientY)); @@ -48,10 +184,12 @@ export default class GestureOverlay extends Touchable { const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc)); if (thumbDoc) { - this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />; + runInAction(() => { + this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />; + }); } - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); document.removeEventListener("touchmove", this.handleHandMove); document.addEventListener("touchmove", this.handleHandMove); document.removeEventListener("touchend", this.handleHandUp); @@ -69,11 +207,9 @@ export default class GestureOverlay extends Touchable { @action handleHandUp = (e: TouchEvent) => { - this.onTouchEnd(e); - if (this.prevPoints.size < 3) { - this._palette = undefined; - document.removeEventListener("touchend", this.handleHandUp); - } + // this.onTouchEnd(e); + this._palette = undefined; + document.removeEventListener("touchend", this.handleHandUp); } @action @@ -115,7 +251,7 @@ export default class GestureOverlay extends Touchable { DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }); actionPerformed = true; } - } + }; const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture", { bubbles: true, @@ -181,7 +317,7 @@ export default class GestureOverlay extends Touchable { } } ) - ) + ); this._points = []; } } @@ -215,7 +351,7 @@ export default class GestureOverlay extends Touchable { render() { return ( - <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}> + <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}> {this.props.children} {this._palette} {this.currentStroke} diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 2a7599fbf..a71015b05 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -8,8 +8,11 @@ const HOLD_DURATION = 1000; export abstract class Touchable<T = {}> extends React.Component<T> { //private holdTimer: NodeJS.Timeout | undefined; - holdTimer: NodeJS.Timeout | undefined; + private holdTimer: NodeJS.Timeout | undefined; + private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; + private endDisposer?: InteractionUtils.MultiTouchEventDisposer; + protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _touchDrag: boolean = false; protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>(); @@ -22,16 +25,27 @@ export abstract class Touchable<T = {}> extends React.Component<T> { * When a touch even starts, we keep track of each touch that is associated with that event */ @action - protected onTouchStart = (e: React.TouchEvent): void => { + protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => { const actualPts: React.Touch[] = []; - for (let i = 0; i < e.targetTouches.length; i++) { - const pt: any = e.targetTouches.item(i); + const te = me.touchEvent; + // loop through all touches on screen + for (let i = 0; i < te.touches.length; i++) { + const pt: any = te.touches.item(i); actualPts.push(pt); - // 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.radiusX > 1 && pt.radiusY > 1) { + 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 (let j = 0; j < te.changedTouches.length; j++) { + const tPt = te.changedTouches.item(j); + 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.radiusX > 1 && pt.radiusY > 1) { + this.prevPoints.set(pt.identifier, pt); + } + } + } } const ptsToDelete: number[] = []; @@ -41,26 +55,30 @@ export abstract class Touchable<T = {}> extends React.Component<T> { } }); + // console.log(ptsToDelete.length); ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); + // console.log(this.prevPoints.size); if (this.prevPoints.size) { switch (this.prevPoints.size) { case 1: - this.handle1PointerDown(e); - e.persist(); + this.handle1PointerDown(te); + te.persist(); // if (this.holdTimer) { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } - this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e), HOLD_DURATION); + this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te), HOLD_DURATION); + // e.stopPropagation(); // console.log(this.holdTimer); break; case 2: - this.handle2PointersDown(e); - break; - case 5: - this.handleHandDown(e); + this.handle2PointersDown(te); + // e.stopPropagation(); break; + // case 5: + // this.handleHandDown(te); + // break; } } } @@ -69,28 +87,30 @@ export abstract class Touchable<T = {}> extends React.Component<T> { * Handle touch move event */ @action - protected onTouch = (e: TouchEvent): void => { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { + const te = me.touchEvent; + const myTouches = InteractionUtils.GetMyTargetTouches(te, 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; if (this.holdTimer) { - console.log("CLEAR") + console.log("CLEAR"); clearTimeout(this.holdTimer); // this.holdTimer = undefined; } + // console.log(myTouches.length); switch (myTouches.length) { case 1: - this.handle1PointerMove(e); + this.handle1PointerMove(te); break; case 2: - this.handle2PointersMove(e); + this.handle2PointersMove(te); break; } - for (let i = 0; i < e.targetTouches.length; i++) { - const pt = e.targetTouches.item(i); + for (let i = 0; i < te.touches.length; i++) { + const pt = te.touches.item(i); if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); @@ -100,11 +120,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> { } @action - protected onTouchEnd = (e: TouchEvent): void => { + protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => { // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up"); // remove all the touches associated with the event - for (let i = 0; i < e.changedTouches.length; i++) { - const pt = e.changedTouches.item(i); + const te = me.touchEvent; + for (let i = 0; i < te.changedTouches.length; i++) { + const pt = te.changedTouches.item(i); if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.delete(pt.identifier); @@ -116,7 +137,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> { console.log("clear"); } this._touchDrag = false; - e.stopPropagation(); + te.stopPropagation(); // if (e.targetTouches.length === 0) { @@ -126,11 +147,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> { if (this.prevPoints.size === 0) { this.cleanUpInteractions(); } + e.stopPropagation(); } cleanUpInteractions = (): void => { - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.removeEndListeners(); } handle1PointerMove = (e: TouchEvent): any => { @@ -144,23 +166,43 @@ export abstract class Touchable<T = {}> extends React.Component<T> { } handle1PointerDown = (e: React.TouchEvent): any => { - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } handle2PointersDown = (e: React.TouchEvent): any => { - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } handle1PointerHoldStart = (e: React.TouchEvent): any => { e.stopPropagation(); e.preventDefault(); - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); + } + + addMoveListeners = () => { + const handler = (e: Event) => this.onTouch(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); + document.addEventListener("dashOnTouchMove", handler); + this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler); + } + + removeMoveListeners = () => { + this.moveDisposer && this.moveDisposer(); + } + + addEndListeners = () => { + const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent<InteractionUtils.MultiTouchEvent<TouchEvent>>).detail); + document.addEventListener("dashOnTouchEnd", handler); + this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler); + } + + removeEndListeners = () => { + this.endDisposer && this.endDisposer(); } handleHandDown = (e: React.TouchEvent) => { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 992820fc7..b8423af20 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -153,7 +153,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; - this.createDropAndGestureTarget(ele!); //so the whole grid is the drop target? + this.createDashEventsTarget(ele!); //so the whole grid is the drop target? } overlays = (doc: Doc) => { @@ -309,7 +309,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { docList={docList} parent={this} type={type} - createDropTarget={this.createDropAndGestureTarget} + createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} />; } @@ -342,7 +342,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { docList={docList} parent={this} type={type} - createDropTarget={this.createDropAndGestureTarget} + createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} setDocHeight={this.setDocHeight} />; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 73dc7edc6..4133956fc 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -24,6 +24,7 @@ import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { Networking } from "../../Network"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; +import { InteractionUtils } from "../../util/InteractionUtils"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc) => boolean; @@ -49,17 +50,20 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; private _childLayoutDisposer?: IReactionDisposer; - protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); this.gestureDisposer && this.gestureDisposer(); + this.multiTouchDisposer && this.multiTouchDisposer(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); + this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); } } protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view - this.createDropAndGestureTarget(ele); + this.createDashEventsTarget(ele); } componentDidMount() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 84945c6e6..3894e9d63 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -326,29 +326,30 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action handle1PointerDown = (e: React.TouchEvent) => { - const pt = e.targetTouches.item(0); - if (pt) { - this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; - if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); - // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) { - // e.stopPropagation(); - // e.preventDefault(); - // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY); - // this._points.push({ X: point[0], Y: point[1] }); - // } - if (InkingControl.Instance.selectedTool === InkTool.None) { - this._lastX = pt.pageX; - this._lastY = pt.pageY; - e.stopPropagation(); - e.preventDefault(); - } - else { - e.stopPropagation(); - e.preventDefault(); + if (!e.nativeEvent.cancelBubble) { + // const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const pt = e.changedTouches.item(0); + if (pt) { + this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; + if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); + // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) { + // e.stopPropagation(); + // e.preventDefault(); + // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY); + // this._points.push({ X: point[0], Y: point[1] }); + // } + if (InkingControl.Instance.selectedTool === InkTool.None) { + this._lastX = pt.pageX; + this._lastY = pt.pageY; + e.preventDefault(); + } + else { + e.preventDefault(); + } } } } @@ -395,8 +396,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.removeEndListeners(); } @action @@ -482,7 +483,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.pan(pt); } } - e.stopPropagation(); + // e.stopPropagation(); e.preventDefault(); } } @@ -527,7 +528,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } } - e.stopPropagation(); + // e.stopPropagation(); e.preventDefault(); } } @@ -535,18 +536,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action handle2PointersDown = (e: React.TouchEvent) => { if (!e.nativeEvent.cancelBubble && this.props.active(true)) { - const pt1: React.Touch | null = e.targetTouches.item(0); - const pt2: React.Touch | null = e.targetTouches.item(1); - if (!pt1 || !pt2) return; + // const pt1: React.Touch | null = e.targetTouches.item(0); + // const pt2: React.Touch | null = e.targetTouches.item(1); + // // if (!pt1 || !pt2) return; + const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const pt1 = myTouches[0]; + const pt2 = myTouches[1]; const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; this._lastX = centerX; this._lastY = centerY; - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); e.stopPropagation(); } } @@ -554,8 +558,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { cleanUpInteractions = () => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.removeEndListeners(); } @action @@ -989,9 +993,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document if (!this.extensionDoc) return (null); // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale; - return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, + return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }} - onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}> + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ? this.placeholder : this.marqueeView} <CollectionFreeFormOverlayView elements={this.elementFunc} /> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2e0ae09ba..b33bebe7d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -103,6 +103,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; private _titleRef = React.createRef<EditableView>(); + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @@ -177,6 +179,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); + this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @@ -184,7 +187,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); + this._gestureEventDisposer && this._gestureEventDisposer(); + this.multiTouchDisposer && this.multiTouchDisposer(); this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); + this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this))); + this._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); } @action @@ -319,25 +326,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); - if ((e.nativeEvent as any).formattedHandled) e.stopPropagation(); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); + e.stopPropagation(); } } handle1PointerMove = (e: TouchEvent) => { if ((e as any).formattedHandled) { e.stopPropagation; return; } if (e.cancelBubble && this.active) { - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); } else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) { const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) { if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) { - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.cleanUpInteractions(); this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); } } @@ -352,10 +358,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu e.stopPropagation(); e.preventDefault(); - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } } @@ -970,7 +976,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu width: animwidth, height: animheight, opacity: this.Document.opacity - }} onTouchStart={this.onTouchStart}> + }}> {this.innards} </div>; } diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 062604458..4b5ad6684 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -31,7 +31,7 @@ export namespace GestureUtils { element.addEventListener("dashOnGesture", handler); return () => { element.removeEventListener("dashOnGesture", handler); - } + }; } export enum Gestures { |