From 3f3dec5c028ea335b90e4326d5a5cc8fd5cdd7d1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 3 Aug 2023 08:36:13 +0700 Subject: [PATCH] refactor(web): drops ComplexGestureSource --- .../engine/headless/complexGestureSource.ts | 115 ------------------ .../gestures/matchers/gestureMatcher.ts | 16 +-- .../engine/headless/touchpointCoordinator.ts | 47 +++---- .../gesture-recognizer/src/engine/index.ts | 1 - .../src/engine/touchEventEngine.ts | 19 ++- 5 files changed, 35 insertions(+), 163 deletions(-) delete mode 100644 common/web/gesture-recognizer/src/engine/headless/complexGestureSource.ts diff --git a/common/web/gesture-recognizer/src/engine/headless/complexGestureSource.ts b/common/web/gesture-recognizer/src/engine/headless/complexGestureSource.ts deleted file mode 100644 index 9a8e5f25e1..0000000000 --- a/common/web/gesture-recognizer/src/engine/headless/complexGestureSource.ts +++ /dev/null @@ -1,115 +0,0 @@ -import EventEmitter from "eventemitter3"; -import { SerializedSimpleGestureSource, SimpleGestureSource } from "./simpleGestureSource.js"; - -/** - * Documents the expected typing of serialized versions of the `ComplexGestureSource` class. - */ -export interface SerializedComplexGestureSource { - touchpoints: SerializedSimpleGestureSource[]; - // gesture: Gesture; -} - -interface EventMap { - 'newcontact': (contact: SimpleGestureSource) => void; - 'end': () => void; - 'cancel': () => void; -} - - -/** - * Models all ongoing contact that is considered part of the same single gesture - * or sequence of chained Gestures over time. This may or may not involve - * multiple touch contact points / "SimpleGestureSource" instances. - * - * Note that multiple chained gestures may arise over the lifetime of a single - * instance of this class. For example, detecting a multitap requires - * multiple contact points over time, possibly with each tap arising as a - * potential 'last' tap gesture before new ones are received to continue the - * sequence. - * - * _Supported events_: - * - * `'cancel'`: all gesture recognition for this input is to be cancelled - * and left incomplete. - * - Provides no parameters. - * - * `'end'`: all gesture recognition for this input is to be resolved. - * - Provides no parameters. - */ -export class ComplexGestureSource extends EventEmitter> { - public readonly touchpoints: SimpleGestureSource[]; - - // --- Future design aspects --- - // private _gesture: Gesture; - // public get gesture() { return this._gesture }; - - private isActive = true; - - constructor(basePoint: SimpleGestureSource) { - super(); - - this.touchpoints = [ basePoint ]; - this._attachPointHooks(basePoint); - } - - private _attachPointHooks(touchpoint: SimpleGestureSource) { - touchpoint.path.on('complete', () => { - this.isActive = false; - this.emit('end'); - this.removeAllListeners(); - }); - - touchpoint.path.on('invalidated', () => { - this.isActive = false; - this.emit('cancel'); - this.removeAllListeners(); - }) - } - - addTouchpoint(touchpoint: SimpleGestureSource) { - this.touchpoints.push(touchpoint); - this._attachPointHooks(touchpoint); - } - - cancel() { - if(this.isActive) { - for(let point of this.touchpoints) { - point.terminate(true); - } - } - } - - end() { - if(this.isActive) { - for(let point of this.touchpoints) { - point.terminate(false); - } - } - } - - public get hasSyncedPaths(): boolean { - if(this.touchpoints.length <= 1) { - return true; - } else { - const timestamp = this.touchpoints[0].currentSample.t; - - for(let i=1; i < this.touchpoints.length; i++) { - if(this.touchpoints[i].currentSample.t != timestamp) { - return false; - } - } - - return true; - } - } - - /** - * Creates a serialization-friendly version of this instance for use by - * `JSON.stringify`. - */ - toJSON(): SerializedComplexGestureSource { - return { - touchpoints: this.touchpoints.map((point) => point.toJSON()) - }; - } -} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts index e95427e5f6..a2519c80d2 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts @@ -1,4 +1,3 @@ -import { ComplexGestureSource } from "../../complexGestureSource.js"; import { SimpleGestureSource, SimpleGestureSourceSubview } from "../../simpleGestureSource.js"; import { GestureModel, GestureResolution, GestureResolutionSpec, RejectionDefault, ResolutionItemSpec } from "../specs/gestureModel.js"; @@ -26,26 +25,26 @@ export class GestureMatcher { private readonly publishedPromise: ManagedPromise>; // unsure on the actual typing at the moment. private _result: MatchResult; - private baseSource: ComplexGestureSource; + private baseSources: SimpleGestureSource[]; public get promise() { return this.publishedPromise.corePromise; } - constructor(model: GestureModel, sourceObj: ComplexGestureSource | GestureMatcher) { + constructor(model: GestureModel, sourceObj: SimpleGestureSource | GestureMatcher) { /* c8 ignore next 5 */ if(!model || !sourceObj) { throw new Error("Construction of GestureMatcher requires a gesture-model spec and a source for related contact points."); - } else if(!model.sustainTimer && sourceObj instanceof ComplexGestureSource && sourceObj.touchpoints.length == 0) { + } else if(!model.sustainTimer && !(sourceObj)) { throw new Error("If the provided gesture-model spec lacks a sustain timer, there must be an active contact point."); } // We condition on ComplexGestureSource since some unit tests mock the other type without // instantiating the actual type. - const predecessor = sourceObj instanceof ComplexGestureSource ? null : sourceObj; - const source = predecessor ? null : (sourceObj as ComplexGestureSource); + const predecessor = sourceObj instanceof SimpleGestureSource ? null : sourceObj; + const source = predecessor ? null : (sourceObj as SimpleGestureSource); - this.baseSource = predecessor?.baseSource || source; + this.baseSources = predecessor?.baseSources || [source]; this.predecessor = predecessor; this.publishedPromise = new ManagedPromise(); @@ -62,7 +61,7 @@ export class GestureMatcher { this.pathMatchers = []; const sourceTouchpoints: SimpleGestureSource[] = source - ? source.touchpoints + ? [ source ] : predecessor.pathMatchers.map((matcher) => matcher.source); let offset = 0; @@ -264,6 +263,7 @@ export class GestureMatcher { } } + this.baseSources.push(simpleSource); this.addContactInternal(simpleSource.constructSubview(false, true)); } diff --git a/common/web/gesture-recognizer/src/engine/headless/touchpointCoordinator.ts b/common/web/gesture-recognizer/src/engine/headless/touchpointCoordinator.ts index 1ead0951cd..17418852ab 100644 --- a/common/web/gesture-recognizer/src/engine/headless/touchpointCoordinator.ts +++ b/common/web/gesture-recognizer/src/engine/headless/touchpointCoordinator.ts @@ -1,7 +1,6 @@ import EventEmitter from "eventemitter3"; import { InputEngineBase } from "./inputEngineBase.js"; -import { ComplexGestureSource } from "./complexGestureSource.js"; -import { SimpleGestureSource } from "./simpleGestureSource.js"; +import { SimpleGestureSource, SimpleGestureSourceSubview } from "./simpleGestureSource.js"; interface EventMap { /** @@ -9,7 +8,7 @@ interface EventMap { * @param input * @returns */ - 'inputstart': (input: ComplexGestureSource) => void; + 'inputstart': (input: SimpleGestureSource) => void; } /** @@ -23,8 +22,7 @@ interface EventMap { export class TouchpointCoordinator extends EventEmitter> { private inputEngines: InputEngineBase[]; - private _activeSourcesMap: {[id: string]: ComplexGestureSource} = {}; - private _activeSources: ComplexGestureSource[] = []; + private _activeSources: SimpleGestureSource[] = []; public constructor() { super(); @@ -36,59 +34,42 @@ export class TouchpointCoordinator extends EventEmitter) => { + private readonly onNewTrackedPath = async (touchpoint: SimpleGestureSource) => { this.addSimpleSourceHooks(touchpoint); - // ... stuff. + // ... stuff - // If no active ComplexGestureSource entries may match the incoming touchpoint, we have a new - // ComplexGestureSource. - const newInput = this.establishNewComplexSource(touchpoint); - - this.emit('inputstart', newInput); - return false; + this.emit('inputstart', touchpoint); } - private doGestureUpdate(source: ComplexGestureSource) { + private doGestureUpdate(source: SimpleGestureSource) { // Should probably ensure data-updates for multi-contact gestures are synchronized // before proceeding. Single-contact cases are inherently synchronized, of course. // // Should a gesture type have geometric requirements on the current location of active // touchpaths, having a desync during a quick movement could cause the calculated // distance between the locations to be markedly different than expected. - if(!source.hasSyncedPaths) { - return; - } - // TODO: stuff. + // TODO: stuff, including synchronization. Probably do that on the caller, + // rather than here? } private addSimpleSourceHooks(touchpoint: SimpleGestureSource) { - // It will be possible for this._activeInputs[touchpoint.identifier] to change during certain - // gestures, so use that - within each handler - for lookups rather than the current `newInput`. - - // ---------- - - touchpoint.path.on('step', () => this.doGestureUpdate(this._activeSourcesMap[touchpoint.identifier])); + touchpoint.path.on('step', () => this.doGestureUpdate(touchpoint)); touchpoint.path.on('invalidated', () => { // TODO: on cancellation, is there any other cleanup to be done? // Also mark the touchpoint as no longer active. - delete this._activeSourcesMap[touchpoint.identifier]; + let i = this._activeSources.indexOf(touchpoint); + this._activeSources = this._activeSources.splice(i, 1); }); touchpoint.path.on('complete', () => { // TODO: on cancellation, is there any other cleanup to be done? // Also mark the touchpoint as no longer active. - delete this._activeSourcesMap[touchpoint.identifier]; + let i = this._activeSources.indexOf(touchpoint); + this._activeSources = this._activeSources.splice(i, 1); }); } - - private establishNewComplexSource(touchpoint: SimpleGestureSource) { - const newInput = new ComplexGestureSource(touchpoint); - this._activeSourcesMap[touchpoint.identifier] = newInput; - this._activeSources.push(newInput); - return newInput; - } } \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/engine/index.ts b/common/web/gesture-recognizer/src/engine/index.ts index 794d0d7d23..a21b5934d1 100644 --- a/common/web/gesture-recognizer/src/engine/index.ts +++ b/common/web/gesture-recognizer/src/engine/index.ts @@ -4,7 +4,6 @@ export { GestureRecognizer } from "./gestureRecognizer.js"; export { GestureRecognizerConfiguration } from "./configuration/gestureRecognizerConfiguration.js"; export { InputEngineBase } from "./headless/inputEngineBase.js"; export { InputSample } from "./headless/inputSample.js"; -export { SerializedComplexGestureSource, ComplexGestureSource } from "./headless/complexGestureSource.js"; export { SerializedGesturePath, GesturePath } from "./headless/gesturePath.js"; export { SerializedSimpleGestureSource, SimpleGestureSource } from "./headless/simpleGestureSource.js"; export { MouseEventEngine } from "./mouseEventEngine.js"; diff --git a/common/web/gesture-recognizer/src/engine/touchEventEngine.ts b/common/web/gesture-recognizer/src/engine/touchEventEngine.ts index 298ab718b9..0ba2f2e670 100644 --- a/common/web/gesture-recognizer/src/engine/touchEventEngine.ts +++ b/common/web/gesture-recognizer/src/engine/touchEventEngine.ts @@ -94,10 +94,11 @@ export class TouchEventEngine extends InputEventEngine extends InputEventEngine extends InputEventEngine