From ac2403522980c4f920e69fa0691cd1b3e1e52f58 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 5 Oct 2023 10:27:57 +0700 Subject: [PATCH] chore(web): re-establishes base-keyboard out-of-bounds roaming --- .../engine/configuration/paddedZoneSource.ts | 34 ++++++++++++++++--- .../osk/src/keyboard-layout/oskLayerGroup.ts | 15 +++++--- web/src/engine/osk/src/visualKeyboard.ts | 21 ++++++++---- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/common/web/gesture-recognizer/src/engine/configuration/paddedZoneSource.ts b/common/web/gesture-recognizer/src/engine/configuration/paddedZoneSource.ts index 05c8dc8b79d..98a9f21d496 100644 --- a/common/web/gesture-recognizer/src/engine/configuration/paddedZoneSource.ts +++ b/common/web/gesture-recognizer/src/engine/configuration/paddedZoneSource.ts @@ -4,13 +4,17 @@ import { ViewportZoneSource } from "./viewportZoneSource.js"; export class PaddedZoneSource implements RecognitionZoneSource { private readonly root: RecognitionZoneSource; - private edgePadding: { + private _edgePadding: { x: number, y: number, w: number, h: number }; + public get edgePadding() { + return this._edgePadding; + } + /** * Provides a dynamic 'padded' recognition zone based upon offsetting from the borders * of the active page's viewport. @@ -54,12 +58,32 @@ export class PaddedZoneSource implements RecognitionZoneSource { // In case it isn't yet defined. edgePadding = edgePadding || [0, 0, 0, 0]; + this.updatePadding(edgePadding); + } + + /** + * Provides a dynamic 'padded' recognition zone based upon offsetting from the borders + * of another defined zone. + * + * Padding is defined using the standard CSS border & padding spec style: + * - [a]: equal and even padding on all sides + * - [a, b]: top & bottom use `a`, left & right use `b` + * - [a, b, c]: top uses `a`, left & right use `b`, bottom uses `c` + * - [a, b, c, d]: top, right, bottom, then left. + * + * Positive padding reduces the size of the resulting zone; negative padding expands it. + * + * @param rootZoneSource The root zone source object/element to be 'padded' + * @param edgePadding A set of 1 to 4 numbers defining padding per the standard CSS border & padding spec style. + */ + updatePadding(edgePadding: number[]) { + // Modeled after CSS styling definitions... just with preprocessed numbers, not strings. switch(edgePadding.length) { case 1: // all sides equal const val = edgePadding[0]; - this.edgePadding = { + this._edgePadding = { x: val, y: val, w: 2 * val, @@ -68,7 +92,7 @@ export class PaddedZoneSource implements RecognitionZoneSource { break; case 2: // top & bottom, left & right - this.edgePadding = { + this._edgePadding = { x: edgePadding[1], y: edgePadding[0], w: 2 * edgePadding[1], @@ -77,7 +101,7 @@ export class PaddedZoneSource implements RecognitionZoneSource { break; case 3: // top, left & right, bottom - this.edgePadding = { + this._edgePadding = { x: edgePadding[1], y: edgePadding[0], w: 2 * edgePadding[1], @@ -85,7 +109,7 @@ export class PaddedZoneSource implements RecognitionZoneSource { }; case 4: // top, right, bottom, left - this.edgePadding = { + this._edgePadding = { x: edgePadding[3], y: edgePadding[0], w: edgePadding[1] + edgePadding[3], diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts index 110b0da5b8b..4237c5fe2da 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts @@ -116,17 +116,22 @@ export default class OSKLayerGroup { } let row: OSKRow = null; + let bestMatchDistance = Number.MAX_VALUE; + + // Find the row that the touch-coordinate lies within. for(const r of layer.rows) { const rowRect = translation(r.element.getBoundingClientRect()); if(rowRect.top <= coord.targetY && coord.targetY < rowRect.bottom) { row = r; break; - } - } + } else { + const distance = rowRect.top > coord.targetY ? rowRect.top - coord.targetY : coord.targetY - rowRect.bottom; - // If the coordinate isn't even on the keyboard... abort. - if(row == null) { - return null; + if(distance < bestMatchDistance) { + bestMatchDistance = distance; + row = r; + } + } } // Assertion: row no longer `null`. diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 2924f511a28..762bccc759d 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -19,7 +19,8 @@ import { GestureRecognizer, GestureRecognizerConfiguration, GestureSource, - InputSample + InputSample, + PaddedZoneSource } from '@keymanapp/gesture-recognizer'; import { createStyleSheet, getAbsoluteX, getAbsoluteY, StylesheetManager } from 'keyman/engine/dom-utils'; @@ -336,11 +337,16 @@ export default class VisualKeyboard extends EventEmitter implements Ke } private constructGestureEngine(): GestureRecognizer { + const rowCount = this.kbdLayout.layerMap['default'].row.length; + const config: GestureRecognizerConfiguration = { targetRoot: this.element, // document.body is the event root for mouse interactions b/c we need to track // when the mouse leaves the VisualKeyboard's hierarchy. mouseEventRoot: document.body, + // Note: at this point in execution, the value will evaluate to NaN! Height hasn't been set yet. + // BUT: we need to establish the instance now; we can update it later when height _is_ set. + maxRoamingBounds: new PaddedZoneSource(this.element, [-0.333 * this.height / rowCount]), // touchEventRoot: this.element, // is the default itemIdentifier: (sample, target) => { /* ALWAYS use the findNearestKey function. @@ -458,6 +464,10 @@ export default class VisualKeyboard extends EventEmitter implements Ke // -- Scratch-space as gestures start becoming integrated -- // Reordering may follow at some point. + // + // Potential long-term idea: only handle the first stage; delegate future stages to + // specialized handlers for the remainder of the sequence. + // Should work for modipresses, too... I think. if(gestureStage.matchedId == 'special-key-start' && gestureKey.key.spec.baseKeyID == 'K_BKSP') { // Possible enhancement: maybe update the held location for the backspace if there's movement? // But... that seems pretty low-priority. @@ -1465,8 +1475,6 @@ export default class VisualKeyboard extends EventEmitter implements Ke gs.fontSize = this.fontSize.styleString; bs.fontSize = ParsedLengthStyle.forScalar(fs).styleString; - // NEW CODE ------ - // Step 1: have the necessary conditions been met? const fixedSize = this.width && this.height; const computedStyle = getComputedStyle(this.kbdDiv); @@ -1492,9 +1500,10 @@ export default class VisualKeyboard extends EventEmitter implements Ke return; } - // Step 3: perform layout operations. (Handled by 'old code' section below.) - - // END NEW CODE ----------- + // Step 3: perform layout operations. + const paddingZone = this.gestureEngine.config.maxRoamingBounds as PaddedZoneSource; + const rowCount = this.currentLayer.rows.length; + paddingZone.updatePadding([-0.333 * this._computedHeight / rowCount]); // Needs the refreshed layout info to work correctly. if(this.currentLayer) {