Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): beginning of gesture-recognizer integration with the OSK 🐵 #9657

Merged
merged 6 commits into from
Oct 20, 2023

Conversation

jahorton
Copy link
Contributor

@jahorton jahorton commented Oct 2, 2023

It's still quite rough, but this PR integrates the OSK (but not the banner) with the new gesture-recognition engine... but just for "simple tap" operations. Not for any of the other interaction types, such as: longpresses, multitaps, special keys, flicks, held backspaces on touch OSKs, etc.

One of the primary pushes for this PR was full conversion of the OSK's findNearestKey function - the original inspiration for, and the intended target for, the gesture-recognizer's itemIdentifier configuration functor field. This is the part responsible for determining which key is pressed (the inspiration for the "item" abstraction) - something obviously critical for actually using a keyboard. It has been converted for compatibility with the gesture-engine and its assumed coordinate system.

The other push, naturally, was to actually connect the OSK with the new engine. Basic wiring is now in place; any gestures reporting keys directly will produce corresponding key events. This is even enough to trigger modifier keys and allow language switching, even if not as immediately as they were triggered before. That said, complex gestures - such as a longpress's subkey-selection mode - do not trigger any Web-side UI or integration as of yet.

In this PR's present state, there are a few very large, very obvious sections of commented code. These sections have only been partially converted, and there are relevant bits there yet to be properly integrated with the new engine.

@keymanapp-test-bot skip

I mean, I could start writing user tests at this point... but given how old OSK functionality is broken in this PR due to the low state of integration, I feel it's better to delay in order to confusion between the "intentionally broken" parts and the "unintentionally broken" parts that tests would be looking for at this stage.

At this stage, a build failure is expected. It will be resolved within 2 PRs, as of #9719.

@keymanapp-test-bot keymanapp-test-bot bot added the user-test-missing User tests have not yet been defined for the PR label Oct 2, 2023
@keymanapp-test-bot
Copy link

keymanapp-test-bot bot commented Oct 2, 2023

User Test Results

Test specification and instructions

User tests are not required

Test Artifacts

@keymanapp-test-bot keymanapp-test-bot bot added this to the A17S23 milestone Oct 2, 2023
@keymanapp-test-bot keymanapp-test-bot bot removed the user-test-missing User tests have not yet been defined for the PR label Oct 2, 2023
@jahorton jahorton force-pushed the feat/web/base-recognizer-integration branch from 0006296 to 0429a1c Compare October 3, 2023 06:54
@jahorton
Copy link
Contributor Author

jahorton commented Oct 3, 2023

A number of the full-integration KMW tests based upon OSK integration are failing.

... that tracks. I'm ripping out lots of older code and slowly linking the new gesture engine in, but it's incomplete at this stage.

@@ -54,4 +58,129 @@ export default class OSKLayerGroup {
lDiv.appendChild(layerObj.element);
}
}

findNearestKey(coord: Omit<InputSample<KeyElement>, 'item'>): KeyElement {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relocated from visualKeyboard.ts.

* @return {Object} nearest key to touch point
*
**/
findNearestKey(input: InputEventCoordinate, t: HTMLElement): KeyElement {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relocated to oskLayerGroup.ts.

itemPriority: 0,
pathResolutionAction: 'resolve',
timer: {
duration: 500,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only place this magic is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets handled far better within #9698. It... might be the only place within this PR, but I'm unsure of that.

From said PR:

export const DEFAULT_GESTURE_PARAMS: GestureParams = {
longpress: {
permitFlick: true,
flickDist: 5,
waitLength: 500,
noiseTolerance: 10
}
}

Comment on lines +121 to +125
evaluate: (path) => {
if(path.isComplete) {
return 'resolve';
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
evaluate: (path) => {
if(path.isComplete) {
return 'resolve';
}
}
evaluate: path => path.isComplete ? 'resolve' : undefined,

Is undefined really what we should be returning otherwise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export interface PathModel {
/**
* Given a TrackedPath, indicates whether or not the path matches this PathModel.
*
* May return null or undefined to signal 'continue'.
*
* @param path
*/
evaluate(path: GesturePath<any>): 'reject' | 'resolve' | undefined;
}

Looks like I need to update the doc-comment (TrackedPath => GesturePath), but the rest of this holds.

const result = model.pathModel.evaluate(source.path) || 'continue';

If you think it feels better, the actual check is "falsy"-based, so null should work as well.

Comment on lines +76 to +85
evaluate: (path) => {
const stats = path.stats;
if(stats.rawDistance > LongpressDistanceThreshold) {
return 'reject';
}

if(path.isComplete) {
return 'reject';
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
evaluate: (path) => {
const stats = path.stats;
if(stats.rawDistance > LongpressDistanceThreshold) {
return 'reject';
}
if(path.isComplete) {
return 'reject';
}
}
evaluate: path =>
path.stats.rawDistance > LongpressDistanceThreshold ? 'reject'
: path.isComplete ? 'reject'
: undefined,

Comment on lines +93 to +102
evaluate: (path) => {
const stats = path.stats;

// Adds up-flick support!
if(stats.rawDistance > LongpressFlickDistanceThreshold && stats.cardinalDirection == 'n') {
return 'resolve';
}

return MainContactLongpressSourceModel.pathModel.evaluate(path);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
evaluate: (path) => {
const stats = path.stats;
// Adds up-flick support!
if(stats.rawDistance > LongpressFlickDistanceThreshold && stats.cardinalDirection == 'n') {
return 'resolve';
}
return MainContactLongpressSourceModel.pathModel.evaluate(path);
}
evaluate: path =>
// up-flick support
path.stats.rawDistance > LongpressFlickDistanceThreshold &&
path.stats.cardinalDirection == 'n' ? 'resolve'
: MainContactLongpressSourceModel.pathModel.evaluate(path),

Comment on lines +134 to +138
evaluate: (path) => {
if(path.isComplete && !path.wasCancelled) {
return 'resolve';
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
evaluate: (path) => {
if(path.isComplete && !path.wasCancelled) {
return 'resolve';
}
}
evaluate: path => path.isComplete && !path.wasCancelled ? 'resolve' : undefined,

Comment on lines +146 to +150
evaluate: (path) => {
if(path.isComplete && !path.wasCancelled) {
return 'resolve';
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
evaluate: (path) => {
if(path.isComplete && !path.wasCancelled) {
return 'resolve';
}
}
evaluate: path => path.isComplete && !path.wasCancelled ? 'resolve' : undefined,

}
],
sustainTimer: {
duration: 500,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to surface the magic timing constants somewhere together

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The start of that is currently in #9698. Multitap stuff is a follow-up to that - #9740 - so I'll aim to make sure it's handled there.

Comment on lines +308 to +316
// TODO: needs better abstraction, probably.

// But, to get started... we can just use a simple hardcoded approach.
const modifierKeyIds = ['K_SHIFT', 'K_ALT', 'K_CTRL'];
for(const modKeyId of modifierKeyIds) {
if(baseItem.key.spec.id == modKeyId) {
return true;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So modipress is only working for desktop modifier keys? That's going to need significant work still I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering I've done little to actually integrate modipress thus far... yes, it will need significant work.

web/src/engine/osk/src/visualKeyboard.ts Outdated Show resolved Hide resolved
Base automatically changed from refactor/web/common-layout-typing to feature-gestures October 20, 2023 09:12
@jahorton jahorton merged commit 74502c4 into feature-gestures Oct 20, 2023
12 of 14 checks passed
@jahorton jahorton deleted the feat/web/base-recognizer-integration branch October 20, 2023 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants