Merge pull request #9261 from keymanapp/change/web/internal-gesture-src-nomenclature

change(web): nomenclature shift for classes involved in touch contact point tracking 🐵
This commit is contained in:
Joshua Horton 2023-07-13 14:15:59 +07:00 committed by GitHub
commit 7d9a5bfc8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 107 additions and 78 deletions

View file

@ -48,4 +48,12 @@ Further events for that "tracked point" are based on `TrackedPoint.path`. This
perspective of each touch point individually for further processing... effectively splitting multi-touchpoint
events into multiple _individual_ events while keeping the metadata organized over each touchpoint's lifetime.
## From change/web/internal-gesture-src-nomenclature
In case someone has to trace this history on things:
- `TrackedInput` is now `ComplexGestureSource`.
- `TrackedPoint` is now `SimpleGestureSource`.
- `TrackedPath` is now `GesturePath`.

View file

@ -1,11 +1,11 @@
import EventEmitter from "eventemitter3";
import { JSONTrackedPoint, TrackedPoint } from "./trackedPoint.js";
import { SerializedSimpleGestureSource, SimpleGestureSource } from "./simpleGestureSource.js";
/**
* Documents the expected typing of serialized versions of the `TrackedInput` class.
* Documents the expected typing of serialized versions of the `ComplexGestureSource` class.
*/
export interface JSONTrackedInput {
touchpoints: JSONTrackedPoint[];
export interface SerializedComplexGestureSource {
touchpoints: SerializedSimpleGestureSource[];
// gesture: Gesture;
}
@ -16,20 +16,27 @@ interface EventMap {
/**
* Models a single ongoing input event, which may or may not involve multiple
* touchpoints.
* 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.
* and left incomplete.
* - Provides no parameters.
*
* `'end'`: all gesture recognition for this input is to be resolved.
* - Provides no parameters.
*/
export class TrackedInput<HoveredItemType> extends EventEmitter<EventMap> {
public readonly touchpoints: TrackedPoint<HoveredItemType>[];
export class ComplexGestureSource<HoveredItemType> extends EventEmitter<EventMap> {
public readonly touchpoints: SimpleGestureSource<HoveredItemType>[];
// --- Future design aspects ---
// private _gesture: Gesture;
@ -37,14 +44,14 @@ export class TrackedInput<HoveredItemType> extends EventEmitter<EventMap> {
private isActive = true;
constructor(basePoint: TrackedPoint<HoveredItemType>) {
constructor(basePoint: SimpleGestureSource<HoveredItemType>) {
super();
this.touchpoints = [ basePoint ];
this._attachPointHooks(basePoint);
}
private _attachPointHooks(touchpoint: TrackedPoint<HoveredItemType>) {
private _attachPointHooks(touchpoint: SimpleGestureSource<HoveredItemType>) {
touchpoint.path.on('complete', () => {
this.isActive = false;
this.emit('end');
@ -78,7 +85,7 @@ export class TrackedInput<HoveredItemType> extends EventEmitter<EventMap> {
* Creates a serialization-friendly version of this instance for use by
* `JSON.stringify`.
*/
toJSON(): JSONTrackedInput {
toJSON(): SerializedComplexGestureSource {
return {
touchpoints: this.touchpoints.map((point) => point.toJSON())
};

View file

@ -3,9 +3,9 @@ import { InputSample } from "./inputSample.js";
import { CumulativePathStats } from "./cumulativePathStats.js";
/**
* Documents the expected typing of serialized versions of the `TrackedPoint` class.
* Documents the expected typing of serialized versions of the `GesturePath` class.
*/
export type JSONTrackedPath<Type> = {
export type SerializedGesturePath<Type> = {
coords: InputSample<Type>[]; // ensures type match with public class property.
wasCancelled?: boolean;
}
@ -42,7 +42,7 @@ interface EventMap<Type> {
* the most recently-preceding 'segmentation' event.
* - And possibly recognition Promise fulfillment.
*/
export class TrackedPath<Type> extends EventEmitter<EventMap<Type>> {
export class GesturePath<Type> extends EventEmitter<EventMap<Type>> {
private samples: InputSample<Type>[] = [];
private _isComplete: boolean = false;
@ -60,11 +60,11 @@ export class TrackedPath<Type> extends EventEmitter<EventMap<Type>> {
}
/**
* Deserializes a TrackedPath instance from its corresponding JSON.parse() object.
* Deserializes a GesturePath instance from its corresponding JSON.parse() object.
* @param jsonObj
*/
static deserialize<Type>(jsonObj: JSONTrackedPath<Type>): TrackedPath<Type> {
const instance = new TrackedPath<Type>();
static deserialize<Type>(jsonObj: SerializedGesturePath<Type>): GesturePath<Type> {
const instance = new GesturePath<Type>();
instance.samples = [].concat(jsonObj.coords.map((obj) => ({...obj} as InputSample<Type>)));
instance._isComplete = true;
@ -90,7 +90,7 @@ export class TrackedPath<Type> extends EventEmitter<EventMap<Type>> {
*/
extend(sample: InputSample<Type>) {
if(this._isComplete) {
throw new Error("Invalid state: this TrackedPath has already terminated.");
throw new Error("Invalid state: this GesturePath has already terminated.");
}
// The tracked path should emit InputSample events before Segment events and
@ -106,7 +106,7 @@ export class TrackedPath<Type> extends EventEmitter<EventMap<Type>> {
*/
terminate(cancel: boolean = false) {
if(this._isComplete) {
throw new Error("Invalid state: this TrackedPath has already terminated.");
throw new Error("Invalid state: this GesturePath has already terminated.");
}
this.wasCancelled = cancel;
this._isComplete = true;
@ -137,7 +137,7 @@ export class TrackedPath<Type> extends EventEmitter<EventMap<Type>> {
* `JSON.stringify`.
*/
toJSON() {
let jsonClone: JSONTrackedPath<Type> = {
let jsonClone: SerializedGesturePath<Type> = {
// Replicate array and its entries, but with certain fields of each entry missing.
// No .clientX, no .clientY.
coords: [].concat(this.samples.map((obj) => ({

View file

@ -1,12 +1,12 @@
import EventEmitter from "eventemitter3";
import { TrackedPoint } from "./trackedPoint.js";
import { SimpleGestureSource } from "./simpleGestureSource.js";
interface EventMap<HoveredItemType> {
/**
* Indicates that a new, ongoing touchpoint or mouse interaction has begun.
* @param input The instance that tracks all future updates over the lifetime of the touchpoint / mouse interaction.
*/
'pointstart': (input: TrackedPoint<HoveredItemType>) => void;
'pointstart': (input: SimpleGestureSource<HoveredItemType>) => void;
// // idea for line below: to help multitouch gestures keep touchpaths in sync, rather than updated separately
// 'eventcomplete': () => void;
@ -18,7 +18,7 @@ interface EventMap<HoveredItemType> {
* (headlessly).
*/
export abstract class InputEngineBase<HoveredItemType> extends EventEmitter<EventMap<HoveredItemType>> {
private _activeTouchpoints: TrackedPoint<HoveredItemType>[] = [];
private _activeTouchpoints: SimpleGestureSource<HoveredItemType>[] = [];
/**
* @param identifier The identifier number corresponding to the input sequence.
@ -35,7 +35,7 @@ export abstract class InputEngineBase<HoveredItemType> extends EventEmitter<Even
this._activeTouchpoints = this._activeTouchpoints.filter((point) => point.rawIdentifier != identifier);
}
protected addTouchpoint(touchpoint: TrackedPoint<HoveredItemType>) {
protected addTouchpoint(touchpoint: SimpleGestureSource<HoveredItemType>) {
this._activeTouchpoints.push(touchpoint);
}
}

View file

@ -1,22 +1,36 @@
import { InputSample } from "./inputSample.js";
import { JSONTrackedPath, TrackedPath } from "./trackedPath.js";
import { SerializedGesturePath, GesturePath } from "./gesturePath.js";
/**
* Documents the expected typing of serialized versions of the `TrackedPoint` class.
* Documents the expected typing of serialized versions of the `SimpleGestureSource` class.
*/
export type JSONTrackedPoint<HoveredItemType = any> = {
export type SerializedSimpleGestureSource<HoveredItemType = any> = {
isFromTouch: boolean;
path: JSONTrackedPath<HoveredItemType>;
path: SerializedGesturePath<HoveredItemType>;
initialHoveredItem: HoveredItemType
// identifier is not included b/c it's only needed during live processing.
}
/**
* Represents one 'tracked point' involved in a potential / recognized gesture as tracked over time.
* This 'tracked point' corresponds to one touch source as recognized by `Touch.identifier` or to
* Represents all metadata needed internally for tracking a single "touch contact point" / "touchpoint"
* involved in a potential / recognized gesture as tracked over time.
*
* Each instance corresponds to one unique contact point as recognized by `Touch.identifier` or to
* one 'cursor-point' as represented by mouse-based motion.
*
* Refer to https://developer.mozilla.org/en-US/docs/Web/API/Touch and
* https://developer.mozilla.org/en-US/docs/Web/API/Navigator/maxTouchPoints re "touch contact point".
*
* May be one-to-many with recognized gestures: a keyboard longpress interaction generally only has one
* contact point but will have multiple realized gestures / components:
* - longpress: Enough time has elapsed
* - subkey: Subkey from the longpress subkey menu has been selected.
*
* Thus, it is a "gesture source". This is the level needed to model a single contact point, while some
* gestures expect multiple, hence "simple".
*
*/
export class TrackedPoint<HoveredItemType> {
export class SimpleGestureSource<HoveredItemType> {
/**
* Indicates whether or not this tracked point's original source is a DOM `Touch`.
*/
@ -27,19 +41,19 @@ export class TrackedPoint<HoveredItemType> {
*/
public readonly rawIdentifier: number;
private _path: TrackedPath<HoveredItemType>;
private _path: GesturePath<HoveredItemType>;
private static _jsonIdSeed: -1;
/**
* Tracks the coordinates and timestamps of each update for the lifetime of this `TrackedPoint`.
* Tracks the coordinates and timestamps of each update for the lifetime of this `SimpleGestureSource`.
*/
public get path(): TrackedPath<HoveredItemType> {
public get path(): GesturePath<HoveredItemType> {
return this._path;
}
/**
* Constructs a new TrackedPoint instance for tracking updates to an active input point over time.
* Constructs a new SimpleGestureSource instance for tracking updates to an active input point over time.
* @param identifier The system identifier for the input point's events.
* @param initialHoveredItem The initiating event's original target element
* @param isFromTouch `true` if sourced from a `TouchEvent`; `false` otherwise.
@ -47,20 +61,20 @@ export class TrackedPoint<HoveredItemType> {
constructor(identifier: number, isFromTouch: boolean) {
this.rawIdentifier = identifier;
this.isFromTouch = isFromTouch;
this._path = new TrackedPath();
this._path = new GesturePath();
}
/**
* Deserializes a TrackedPoint instance from its serialized-JSON form.
* Deserializes a SimpleGestureSource instance from its serialized-JSON form.
* @param jsonObj The JSON representation to deserialize.
* @param identifier The unique identifier to assign to this instance.
*/
public static deserialize(jsonObj: JSONTrackedPoint, identifier: number) {
public static deserialize(jsonObj: SerializedSimpleGestureSource, identifier: number) {
const id = identifier !== undefined ? identifier : this._jsonIdSeed++;
const isFromTouch = jsonObj.isFromTouch;
const path = TrackedPath.deserialize(jsonObj.path);
const path = GesturePath.deserialize(jsonObj.path);
const instance = new TrackedPoint(id, isFromTouch);
const instance = new SimpleGestureSource(id, isFromTouch);
instance._path = path;
return instance;
}
@ -71,7 +85,7 @@ export class TrackedPoint<HoveredItemType> {
/**
* The identifying metadata returned by the configuration's specified `itemIdentifier` for
* the target of the first `Event` that corresponded to this `TrackedPoint`.
* the target of the first `Event` that corresponded to this `SimpleGestureSource`.
*/
public get initialHoveredItem(): HoveredItemType {
return this.path.coords[0].item;
@ -79,7 +93,7 @@ export class TrackedPoint<HoveredItemType> {
/**
* The identifying metadata returned by the configuration's specified `itemIdentifier` for
* the target of the latest `Event` that corresponded to this `TrackedPoint`.
* the target of the latest `Event` that corresponded to this `SimpleGestureSource`.
*/
public get currentHoveredItem(): HoveredItemType {
return this.path.coords[this.path.coords.length-1].item;
@ -98,8 +112,8 @@ export class TrackedPoint<HoveredItemType> {
* Creates a serialization-friendly version of this instance for use by
* `JSON.stringify`.
*/
toJSON(): JSONTrackedPoint {
let jsonClone: JSONTrackedPoint = {
toJSON(): SerializedSimpleGestureSource {
let jsonClone: SerializedSimpleGestureSource = {
isFromTouch: this.isFromTouch,
initialHoveredItem: this.initialHoveredItem,
path: this.path.toJSON()

View file

@ -296,7 +296,7 @@ export class ConstructingSegment {
}
/**
* The in-construction Segment, as published to `TrackedPath.segments` & `TrackedPath`'s
* The in-construction Segment, as published to `GesturePath.segments` & `GesturePath`'s
* 'segmentation' event.
*/
public get pathSegment() {

View file

@ -554,7 +554,7 @@ export class PathSegmenter {
/**
* A closure used to 'forward' generated Segments, generally to their public-facing
* location on TrackedPath.segments.
* location on GesturePath.segments.
*/
private readonly segmentForwarder: (segment: Segment) => void;

View file

@ -1,7 +1,7 @@
import EventEmitter from "eventemitter3";
import { InputEngineBase } from "./inputEngineBase.js";
import { TrackedInput } from "./trackedInput.js";
import { TrackedPoint } from "./trackedPoint.js";
import { ComplexGestureSource } from "./complexGestureSource.js";
import { SimpleGestureSource } from "./simpleGestureSource.js";
interface EventMap<HoveredItemType> {
/**
@ -9,7 +9,7 @@ interface EventMap<HoveredItemType> {
* @param input
* @returns
*/
'inputstart': (input: TrackedInput<HoveredItemType>) => void;
'inputstart': (input: ComplexGestureSource<HoveredItemType>) => void;
}
/**
@ -23,7 +23,7 @@ interface EventMap<HoveredItemType> {
export class TouchpointCoordinator<HoveredItemType> extends EventEmitter<EventMap<HoveredItemType>> {
private inputEngines: InputEngineBase<HoveredItemType>[];
private _activeInputs: {[id: string]: TrackedInput<HoveredItemType>} = {};
private _activeInputs: {[id: string]: ComplexGestureSource<HoveredItemType>} = {};
public constructor() {
super();
@ -35,8 +35,8 @@ export class TouchpointCoordinator<HoveredItemType> extends EventEmitter<EventMa
this.inputEngines.push(engine);
}
private readonly onNewTrackedPath = (touchpoint: TrackedPoint<HoveredItemType>) => {
const newInput = new TrackedInput<HoveredItemType>(touchpoint);
private readonly onNewTrackedPath = (touchpoint: SimpleGestureSource<HoveredItemType>) => {
const newInput = new ComplexGestureSource<HoveredItemType>(touchpoint);
this._activeInputs[touchpoint.identifier] = newInput;
this.emit('inputstart', newInput);

View file

@ -4,9 +4,9 @@ export { GestureRecognizer } from "./gestureRecognizer.js";
export { GestureRecognizerConfiguration } from "./configuration/gestureRecognizerConfiguration.js";
export { InputEngineBase } from "./headless/inputEngineBase.js";
export { InputSample } from "./headless/inputSample.js";
export { JSONTrackedInput, TrackedInput } from "./headless/trackedInput.js";
export { JSONTrackedPath, TrackedPath } from "./headless/trackedPath.js";
export { JSONTrackedPoint, TrackedPoint } from "./headless/trackedPoint.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";
export { PathSegmenter, Subsegmentation } from "./headless/subsegmentation/pathSegmenter.js";
export { PaddedZoneSource } from './configuration/paddedZoneSource.js';

View file

@ -2,7 +2,7 @@ import { GestureRecognizerConfiguration } from "./configuration/gestureRecognize
import { InputEngineBase } from "./headless/inputEngineBase.js";
import { InputSample } from "./headless/inputSample.js";
import { Nonoptional } from "./nonoptional.js";
import { TrackedPoint } from "./headless/trackedPoint.js";
import { SimpleGestureSource } from "./headless/simpleGestureSource.js";
export abstract class InputEventEngine<HoveredItemType> extends InputEngineBase<HoveredItemType> {
protected readonly config: Nonoptional<GestureRecognizerConfiguration<HoveredItemType>>;
@ -32,7 +32,7 @@ export abstract class InputEventEngine<HoveredItemType> extends InputEngineBase<
}
protected onInputStart(identifier: number, sample: InputSample<HoveredItemType>, target: EventTarget, isFromTouch: boolean) {
const touchpoint = new TrackedPoint<HoveredItemType>(identifier, isFromTouch);
const touchpoint = new SimpleGestureSource<HoveredItemType>(identifier, isFromTouch);
touchpoint.update(sample);
this.addTouchpoint(touchpoint);

View file

@ -23,7 +23,7 @@ export class MouseEventEngine<HoveredItemType> extends InputEventEngine<HoveredI
this._mouseMove = (event: MouseEvent) => this.onMouseMove(event);
this._mouseEnd = (event: MouseEvent) => this.onMouseEnd(event);
// IDs should be unique. Fortunately, they're disambiguated by their corresponding TrackedPoint,
// IDs should be unique. Fortunately, they're disambiguated by their corresponding SimpleGestureSource,
// which has gives a globally-unique string-based identifier based partly on the numeric ID set here.
MouseEventEngine.IDENTIFIER_SEED = 0;
}

View file

@ -1,13 +1,13 @@
import { assert } from 'chai'
import sinon from 'sinon';
import { TrackedPath } from '@keymanapp/gesture-recognizer';
import { GesturePath } from '@keymanapp/gesture-recognizer';
import { timedPromise } from '@keymanapp/web-utils';
// End of "for the integrated style..."
describe("TrackedPath", function() {
describe("FingerPath", function() {
// // File paths need to be from the package's / module's root folder
// let testJSONtext = fs.readFileSync('src/test/resources/json/canaryRecording.json');
@ -17,7 +17,7 @@ describe("TrackedPath", function() {
const spyEventComplete = sinon.fake();
const spyEventInvalidated = sinon.fake();
const touchpath = new TrackedPath();
const touchpath = new GesturePath();
touchpath.on('step', spyEventStep);
touchpath.on('complete', spyEventComplete);
touchpath.on('invalidated', spyEventInvalidated);
@ -50,7 +50,7 @@ describe("TrackedPath", function() {
const spyEventComplete = sinon.fake();
const spyEventInvalidated = sinon.fake();
const touchpath = new TrackedPath();
const touchpath = new GesturePath();
touchpath.on('step', spyEventStep);
touchpath.on('complete', spyEventComplete);
touchpath.on('invalidated', spyEventInvalidated);
@ -82,7 +82,7 @@ describe("TrackedPath", function() {
const spyEventComplete = sinon.fake();
const spyEventInvalidated = sinon.fake();
const touchpath = new TrackedPath();
const touchpath = new GesturePath();
touchpath.on('step', spyEventStep);
touchpath.on('complete', spyEventComplete);
touchpath.on('invalidated', spyEventInvalidated);
@ -104,7 +104,7 @@ describe("TrackedPath", function() {
const spyEventComplete = sinon.fake();
const spyEventInvalidated = sinon.fake();
const touchpath = new TrackedPath();
const touchpath = new GesturePath();
touchpath.on('step', spyEventStep);
touchpath.on('complete', spyEventComplete);
touchpath.on('invalidated', spyEventInvalidated);
@ -137,7 +137,7 @@ describe("TrackedPath", function() {
const spyEventComplete = sinon.fake();
const spyEventInvalidated = sinon.fake();
const touchpath = new TrackedPath();
const touchpath = new GesturePath();
touchpath.on('step', spyEventStep);
touchpath.on('complete', spyEventComplete);
touchpath.on('invalidated', spyEventInvalidated);

View file

@ -1,7 +1,7 @@
import {
InputEngineBase,
JSONTrackedPoint,
TrackedPoint
SerializedSimpleGestureSource,
SimpleGestureSource
} from '@keymanapp/gesture-recognizer';
import { RecordedCoordSequenceSet } from './inputRecording.js';
@ -15,7 +15,7 @@ export class HeadlessInputEngine<Type = any> extends InputEngineBase<Type> {
super();
}
public preparePathPlayback(recordedPoint: JSONTrackedPoint) {
public preparePathPlayback(recordedPoint: SerializedSimpleGestureSource) {
const originalSamples = recordedPoint.path.coords;
const sampleCount = originalSamples.length;
@ -23,7 +23,7 @@ export class HeadlessInputEngine<Type = any> extends InputEngineBase<Type> {
const tailSamples = originalSamples.slice(1);
const pathID = this.PATH_ID_SEED++;
let replayPoint = new TrackedPoint<Type>(pathID, recordedPoint.isFromTouch);
let replayPoint = new SimpleGestureSource<Type>(pathID, recordedPoint.isFromTouch);
replayPoint.update(headSample); // is included before the point is made available.
// Build promises designed to reproduce the events at the correct times.

View file

@ -1,4 +1,4 @@
import { type JSONTrackedInput } from "@keymanapp/gesture-recognizer";
import { type SerializedComplexGestureSource } from "@keymanapp/gesture-recognizer";
import { type FixtureLayoutConfiguration } from "./fixtureLayoutConfiguration.js";
import { type JSONObject } from "./jsonObject.js";
@ -7,6 +7,6 @@ import { type JSONObject } from "./jsonObject.js";
* The top-level object produced by the "Test Sequence Recorder".
*/
export interface RecordedCoordSequenceSet {
inputs: JSONTrackedInput[];
inputs: SerializedComplexGestureSource[];
config: JSONObject<FixtureLayoutConfiguration>;
}

View file

@ -1,5 +1,5 @@
import {
TrackedPoint,
SimpleGestureSource,
type InputSample
} from "@keymanapp/gesture-recognizer";
@ -194,7 +194,7 @@ export class InputSequenceSimulator<HoveredItemType> {
for(let index=0; index < inputs.length; index++) {
// TODO: does not iterate over all touchpoints. Not that we can have more than one at present...
const touchpoint = TrackedPoint.deserialize(inputs[index].touchpoints[0], index);
const touchpoint = SimpleGestureSource.deserialize(inputs[index].touchpoints[0], index);
const indexInSequence = sequenceProgress[index];
if(indexInSequence == Number.MAX_VALUE) {
@ -207,7 +207,7 @@ export class InputSequenceSimulator<HoveredItemType> {
}
}
const touchpoint = TrackedPoint.deserialize(inputs[selectedSequence].touchpoints[0], selectedSequence);
const touchpoint = SimpleGestureSource.deserialize(inputs[selectedSequence].touchpoints[0], selectedSequence);
const indexInSequence = sequenceProgress[selectedSequence];
let state: string = "move";

View file

@ -1,4 +1,4 @@
import { TrackedInput } from "@keymanapp/gesture-recognizer";
import { ComplexGestureSource } from "@keymanapp/gesture-recognizer";
import { HostFixtureLayoutController } from "./hostFixtureLayoutController.js";
import { RecordedCoordSequenceSet } from "./inputRecording.js";
@ -7,11 +7,11 @@ import { RecordedCoordSequenceSet } from "./inputRecording.js";
* verification itself.
*/
type WrappedInputSequence = TrackedInput<any>;
type WrappedInputSequence = ComplexGestureSource<any>;
export class SequenceRecorder {
controller: HostFixtureLayoutController;
records: {[identifier: string]: TrackedInput<any>} = {};
records: {[identifier: string]: ComplexGestureSource<any>} = {};
/**
* Tracks the order in which each sequence was first detected.