From 326eaa27005e90a8b2cb0fd3985bbc469a3d2f43 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 19 Apr 2024 10:02:24 +0700 Subject: [PATCH 1/4] chore(web): type-related prep for touch-spec process deferral --- .../src/keyboards/activeLayout.ts | 6 ++++-- .../src/keyboards/defaultLayouts.ts | 18 ++++++++---------- .../src/keyboards/keyboard.ts | 6 ++++-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 89156f8d9b8..115171d7be1 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -9,6 +9,8 @@ import type Keyboard from "./keyboard.js"; import { TouchLayout } from "@keymanapp/common-types"; import TouchLayoutDefaultHint = TouchLayout.TouchLayoutDefaultHint; import TouchLayoutFlick = TouchLayout.TouchLayoutFlick; +import TouchLayoutSpec = TouchLayout.TouchLayoutPlatform; +import TouchLayerSpec = TouchLayout.TouchLayoutLayer; import { type DeviceSpec } from "@keymanapp/web-utils"; // TS 3.9 changed behavior of getters to make them @@ -806,7 +808,7 @@ export class ActiveLayout implements LayoutFormFactor{ } } - static sanitize(rawLayout: LayoutFormFactor) { + static sanitize(rawLayout: TouchLayoutSpec) { ActiveLayout.correctLayerEmptyRowBug(rawLayout.layer); for(const layer of rawLayout.layer) { @@ -819,7 +821,7 @@ export class ActiveLayout implements LayoutFormFactor{ * @param layout * @param formFactor */ - static polyfill(layout: LayoutFormFactor, keyboard: Keyboard, formFactor: DeviceSpec.FormFactor): ActiveLayout { + static polyfill(layout: TouchLayoutSpec, keyboard: Keyboard, formFactor: DeviceSpec.FormFactor): ActiveLayout { /* c8 ignore start */ if(layout == null) { throw new Error("Cannot build an ActiveLayout for a null specification."); diff --git a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts index 2aa96db209f..e1efa9847cf 100644 --- a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts +++ b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts @@ -6,7 +6,7 @@ import { Version, deepCopy } from "@keymanapp/web-utils"; import { TouchLayout } from "@keymanapp/common-types"; -import LayoutFormFactorBase = TouchLayout.TouchLayoutPlatform; +import LayoutFormFactorSpec = TouchLayout.TouchLayoutPlatform; import LayoutLayerBase = TouchLayout.TouchLayoutLayer; export type LayoutRow = TouchLayout.TouchLayoutRow; export type LayoutKey = TouchLayout.TouchLayoutKey; @@ -33,15 +33,13 @@ export interface LayoutLayer extends LayoutLayerBase { aligned?: boolean }; -export interface LayoutFormFactor extends LayoutFormFactorBase { - // To facilitate those post-processing elements. - layer: LayoutLayer[] +export interface LayoutFormFactor extends Omit { }; export type LayoutSpec = { - "desktop"?: LayoutFormFactor, - "phone"?: LayoutFormFactor, - "tablet"?: LayoutFormFactor + "desktop"?: LayoutFormFactorSpec, + "phone"?: LayoutFormFactorSpec, + "tablet"?: LayoutFormFactorSpec } const KEY_102_WIDTH = 200; @@ -93,7 +91,7 @@ export class Layouts { * @param {string} formFactor (really utils.FormFactor) * @return {LayoutFormFactor} */ - static buildDefaultLayout(PVK, keyboard: Keyboard, formFactor: string): LayoutFormFactor { + static buildDefaultLayout(PVK, keyboard: Keyboard, formFactor: string): LayoutFormFactorSpec { // Build a layout using the default for the device var layoutType=formFactor; @@ -114,7 +112,7 @@ export class Layouts { } // Clone the default layout object for this device - var layout: LayoutFormFactor = deepCopy(Layouts.dfltLayout[layoutType]); + var layout: LayoutFormFactorSpec = deepCopy(Layouts.dfltLayout[layoutType]); var n,layers=layout['layer'], keyLabels: KLS=PVK['KLS'], key102=PVK['K102']; var i, j, k, m, row, rows: LayoutRow[], key: LayoutKey, keys: LayoutKey[]; @@ -236,7 +234,7 @@ export class Layouts { // *** Step 2: Layer objects now exist; time to fill them with the appropriate key labels and key styles *** for(n=0; n Date: Fri, 19 Apr 2024 10:13:17 +0700 Subject: [PATCH 2/4] refactor(web): gesture-check preprocessing, removal of redundant param --- .../src/keyboards/activeLayout.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 115171d7be1..35a472b056b 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -530,12 +530,10 @@ export class ActiveRow implements LayoutRow { static polyfill( row: LayoutRow, - keyboard: Keyboard, layout: ActiveLayout, displayLayer: string, totalWidth: number, - proportionalY: number, - analysisFlagObj: AnalysisMetadata + proportionalY: number ) { // Apply defaults, setting the width and other undefined properties for each key let keys=row['key']; @@ -565,16 +563,6 @@ export class ActiveRow implements LayoutRow { const processedKey = new ActiveKey(key, layout, displayLayer); keys[j] = processedKey; - - if(processedKey.sk) { - analysisFlagObj.hasLongpresses = true; - } - if(processedKey.multitap) { - analysisFlagObj.hasMultitaps = true; - } - if(processedKey.flick) { - analysisFlagObj.hasFlicks = true; - } } /* The calculations here are effectively 'virtualized'. When used with the OSK, the VisualKeyboard @@ -678,7 +666,7 @@ export class ActiveLayer implements LayoutLayer { } } - static polyfill(layer: LayoutLayer, keyboard: Keyboard, layout: ActiveLayout, analysisFlagObj: AnalysisMetadata) { + static polyfill(layer: LayoutLayer, layout: ActiveLayout) { layer.aligned=false; // Create a DIV for each row of the group @@ -711,7 +699,7 @@ export class ActiveLayer implements LayoutLayer { for(let i=0; i Date: Fri, 19 Apr 2024 10:43:02 +0700 Subject: [PATCH 3/4] feat(web): touch-layout layer spec processing now deferred until needed --- .../src/keyboards/activeLayout.ts | 52 +++++++++++-------- .../src/keyboards/defaultLayouts.ts | 2 +- .../src/keyboards/keyboard.ts | 10 ++-- web/src/engine/osk/src/visualKeyboard.ts | 6 +++ 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 35a472b056b..6089ce3b0af 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -745,7 +745,11 @@ export class ActiveLayer implements LayoutLayer { } export class ActiveLayout implements LayoutFormFactor{ - layer: ActiveLayer[]; + /** + * Holds all layer specifications for the layout. There is no guarantee that they + * have been fully preprocessed. + */ + layer: TouchLayerSpec[]; font: string; keyLabels: boolean; isDefault?: boolean; @@ -766,8 +770,25 @@ export class ActiveLayout implements LayoutFormFactor{ } + /** + * Returns a fully preprocessed version of the specified layer spec. + * @param layerId + * @returns + */ @Enumerable getLayer(layerId: string): ActiveLayer { + if(!this.layerMap[layerId]) { + const spec = this.layer.find((layerSpec) => layerSpec.id == layerId); + if(!spec) { + return null; + } + + // Prepare the layer-spec for actual use. + ActiveLayer.sanitize(spec); + ActiveLayer.polyfill(spec, this); + this.layerMap[layerId] = spec as ActiveLayer; + } + return this.layerMap[layerId]; } @@ -786,10 +807,10 @@ export class ActiveLayout implements LayoutFormFactor{ static correctLayerEmptyRowBug(layers: LayoutLayer[]) { for(let n=0; n=0; i--) { - if(!Array.isArray(rows[i]['key']) || rows[i]['key'].length == 0) { + if(!Array.isArray(rows[i].key) || rows[i].key.length == 0) { rows.splice(i, 1) } } @@ -798,10 +819,6 @@ export class ActiveLayout implements LayoutFormFactor{ static sanitize(rawLayout: TouchLayoutSpec) { ActiveLayout.correctLayerEmptyRowBug(rawLayout.layer); - - for(const layer of rawLayout.layer) { - ActiveLayer.sanitize(layer); - } } /** @@ -843,11 +860,8 @@ export class ActiveLayout implements LayoutFormFactor{ } // Create a separate OSK div for each OSK layer, only one of which will ever be visible - var n: number; let layerMap: {[layerId: string]: ActiveLayer} = {}; - let layers=layout.layer; - // Add class functions to the existing layout object, allowing it to act as an ActiveLayout. let dummy = new ActiveLayout(); for(let key in dummy) { @@ -856,24 +870,20 @@ export class ActiveLayout implements LayoutFormFactor{ } } - let aLayout = layout as ActiveLayout; + let aLayout = layout as unknown as ActiveLayout; aLayout.keyboard = keyboard; aLayout.formFactor = formFactor; - - for(n=0; n entry.id == 'caps')) { - const defaultLayer = layout.layer.find((entry) => entry.id == 'default') as ActiveLayer; - const shiftLayer = layout.layer.find((entry) => entry.id == 'shift') as ActiveLayer; + // Triggers preprocessing for both default and shift layers. They're the + // most-frequently referenced, at least. + const defaultLayer = aLayout.getLayer('default') as ActiveLayer; + const shiftLayer = aLayout.getLayer('shift') as ActiveLayer; const defaultShift = defaultLayer.getKey('K_SHIFT'); const shiftShift = shiftLayer ?.getKey('K_SHIFT'); @@ -898,7 +908,7 @@ export class ActiveLayout implements LayoutFormFactor{ aLayout.hasLongpresses = analysisMetadata.hasLongpresses; aLayout.hasMultitaps = analysisMetadata.hasMultitaps; - aLayout.layerMap = layerMap; + // All layers are lazy-processed, with the usual processing applied when first referenced. return aLayout; } diff --git a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts index e1efa9847cf..172b63d070c 100644 --- a/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts +++ b/common/web/keyboard-processor/src/keyboards/defaultLayouts.ts @@ -33,7 +33,7 @@ export interface LayoutLayer extends LayoutLayerBase { aligned?: boolean }; -export interface LayoutFormFactor extends Omit { +export interface LayoutFormFactor extends LayoutFormFactorSpec { }; export type LayoutSpec = { diff --git a/common/web/keyboard-processor/src/keyboards/keyboard.ts b/common/web/keyboard-processor/src/keyboards/keyboard.ts index b91c9f03a8d..9a6c39d533b 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboard.ts @@ -4,7 +4,7 @@ import { ActiveKey, ActiveLayout, ActiveSubKey } from "./activeLayout.js"; import KeyEvent from "../text/keyEvent.js"; import type OutputTarget from "../text/outputTarget.js"; import { TouchLayout } from "@keymanapp/common-types"; -import TouchLayoutSpec = TouchLayout.TouchLayoutPlatform; +type TouchLayoutSpec = TouchLayout.TouchLayoutPlatform & { isDefault?: boolean}; import type { ComplexKeyboardStore } from "../text/kbdInterface.js"; @@ -403,7 +403,7 @@ export default class Keyboard { // Final check - do we construct a layout, or is this a case where helpText / insertHelpHTML should take over? if(rawSpecifications) { // Now to generate a layout from our raw specifications. - let layout = this._layouts[formFactor] = Layouts.buildDefaultLayout(rawSpecifications, this, formFactor) as ActiveLayout; + let layout: TouchLayoutSpec = this._layouts[formFactor] = Layouts.buildDefaultLayout(rawSpecifications, this, formFactor); layout.isDefault = true; return layout; } else { @@ -425,11 +425,13 @@ export default class Keyboard { if(rawLayout) { // Prevents accidentally reprocessing layouts; it's a simple enough check. if(this.layoutStates[formFactor] == LayoutState.NOT_LOADED) { - rawLayout = ActiveLayout.polyfill(rawLayout, this, formFactor); + const layout = ActiveLayout.polyfill(rawLayout, this, formFactor); this.layoutStates[formFactor] = LayoutState.POLYFILLED; + return layout; + } else { + return rawLayout as unknown as ActiveLayout; } - return rawLayout as ActiveLayout; } else { return null; } diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 09a9b092d3f..6b1cd50075e 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -1294,6 +1294,12 @@ export default class VisualKeyboard extends EventEmitter implements Ke return allottedHeight; } + /* + Note: these may not be fully preprocessed yet! + + However, any "empty row bug" preprocessing has been applied, and that's + what we care about here. + */ const layers = this.layerGroup.spec.layer; let oskHeight = 0; From 7166428d72edbb95bf3c0938d14ea41abb7f3af4 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 21 Jun 2024 08:38:53 +0700 Subject: [PATCH 4/4] fix(web): check for longpresses with .sk, not .sp --- common/web/keyboard-processor/src/keyboards/activeLayout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index ac00924d7ae..7b293db93c3 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -877,7 +877,7 @@ export class ActiveLayout implements LayoutFormFactor{ for(let layer of layout.layer) { for(let row of layer.row) { for(let key of row.key) { - analysisMetadata.hasLongpresses ||= !!key.sp; + analysisMetadata.hasLongpresses ||= !!key.sk; analysisMetadata.hasFlicks ||= !!key.flick; analysisMetadata.hasMultitaps ||= !!key.multitap; }