Skip to content

Commit

Permalink
change(web): more efficient key-spec processing
Browse files Browse the repository at this point in the history
  • Loading branch information
jahorton committed Apr 23, 2024
1 parent ad6ff80 commit b9cfaa1
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 130 deletions.
224 changes: 111 additions & 113 deletions common/web/keyboard-processor/src/keyboards/activeLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class ActiveKeyBase {
proportionalWidth: number;

// While they're only valid on ActiveKey, spec'ing them here makes references more concise within the OSK.
sk?: ActiveKey[];
sk?: ActiveSubKey[];
multitap?: ActiveSubKey[];
flick?: TouchLayout.TouchLayoutFlick;

Expand Down Expand Up @@ -214,6 +214,24 @@ export class ActiveKeyBase {
return new KeyEvent(val);
}

constructor();
constructor(spec: LayoutKey | LayoutSubKey, layout: ActiveLayout, displayLayer: string);
constructor(spec?: LayoutKey | LayoutSubKey, layout?: ActiveLayout, displayLayer?: string) {
// First things first: this class's fields are designed to match that of the spec.
Object.assign(this, spec);

if(!this.text && typeof this.id == 'string') {
this.text = ActiveKey.unicodeIDToText(this.id);
}

this.displayLayer = displayLayer;
this.layer = this.layer || displayLayer;

// Compute the key's base KeyEvent properties for use in future event generation
// It's actually somewhat expensive to do this at the start, so we do a lazy-init.
this._baseKeyEvent = () => this.constructBaseKeyEvent(layout, displayLayer);
}

/**
* Converts key IDs of the U_* form to their corresponding UTF-16 text.
* If an ID not matching the pattern is received, returns null.
Expand Down Expand Up @@ -318,66 +336,102 @@ export class ActiveKeyBase {
rawKey.text ||= ActiveKey.DEFAULT_KEY.text;
}

static polyfill(key: LayoutKey, keyboard: Keyboard, layout: ActiveLayout, displayLayer: string, analysisFlagObj?: AnalysisMetadata) {
analysisFlagObj ||= {
hasFlicks: false,
hasLongpresses: false,
hasMultitaps: false
}
@Enumerable
private constructBaseKeyEvent(layout: ActiveLayout, displayLayer: string) {
// Get key name and keyboard shift state (needed only for default layouts and physical keyboard handling)
// Note - virtual keys should be treated case-insensitive, so we force uppercasing here.
let layer = this.layer || displayLayer || '';
let keyName= this.id ? this.id.toUpperCase() : null;

// Add class functions to the existing layout object, allowing it to act as an ActiveLayout.
let dummy = new ActiveKeyBase();
let proto = Object.getPrototypeOf(dummy);

for(let prop in dummy) {
if(!key.hasOwnProperty(prop)) {
let descriptor = Object.getOwnPropertyDescriptor(proto, prop);
if(descriptor) {
// It's a computed property! Copy the descriptor onto the key's object.
Object.defineProperty(key, prop, descriptor);
} else {
key[prop] = dummy[prop];
// Start: mirrors _GetKeyEventProperties

// First check the virtual key, and process shift, control, alt or function keys
let props: KeyEventSpec = {
// Override key shift state if specified for key in layout (corrected for popup keys KMEW-93)
Lmodifiers: Codes.getModifierState(layer),
Lstates: Codes.getStateFromLayer(layer),
Lcode: keyName ? Codes.keyCodes[keyName] : 0,
LisVirtualKey: true,
vkCode: 0,
kName: keyName,
kLayer: layer,
kbdLayer: displayLayer,
kNextLayer: this.nextlayer,
device: null,
isSynthetic: true
};

let Lkc: KeyEvent = new KeyEvent(props);

if(layout.keyboard) {
let keyboard = layout.keyboard;

// Include *limited* support for mnemonic keyboards (Sept 2012)
// If a touch layout has been defined for a mnemonic keyout, do not perform mnemonic mapping for rules on touch devices.
if(keyboard.isMnemonic && !(layout.isDefault && layout.formFactor != 'desktop')) {
if(Lkc.Lcode != Codes.keyCodes['K_SPACE']) { // exception required, March 2013
// Jan 2019 - interesting that 'K_SPACE' also affects the caps-state check...
Lkc.vkCode = Lkc.Lcode;
this.isMnemonic = true;
}
} else {
Lkc.vkCode=Lkc.Lcode;
}

// Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic
if(!keyboard.definesPositionalOrMnemonic) {
Lkc.Lcode = KeyMapping._USKeyCodeToCharCode(Lkc);
Lkc.LisVirtualKey=false;
}
}

if(!key.text && typeof key.id == 'string') {
key.text = ActiveKey.unicodeIDToText(key.id);
return Lkc;
}
}


export class ActiveKey extends ActiveKeyBase implements LayoutKey {
public getSubkey(coreID: string): ActiveSubKey {
if(this.sk) {
for(let key of this.sk) {
if(key.coreID == coreID) {
return key;
}
}
}

return null;
}

constructor();
constructor(spec: LayoutKey, layout: ActiveLayout, displayLayer: string);
constructor(spec?: LayoutKey, layout?: ActiveLayout, displayLayer?: string) {
super(spec, layout, displayLayer);

// Ensure subkeys are also properly extended.
if(key.sk) {
analysisFlagObj.hasLongpresses = true;
for(let subkey of key.sk) {
ActiveSubKey.polyfill(subkey, keyboard, layout, displayLayer, analysisFlagObj);
const sk = this.sk;
if(sk) {
for(let i=0; i < sk.length; i++) {
sk[i] = new ActiveSubKey(sk[i], layout, displayLayer);
}
}

// Also multitap keys.
if(key.multitap) {
analysisFlagObj.hasMultitaps = true;
for(let mtKey of key.multitap) {
ActiveSubKey.polyfill(mtKey, keyboard, layout, displayLayer, analysisFlagObj);
const multitap = this.multitap;
if(multitap) {
for(let i=0; i < multitap.length; i++) {
multitap[i] = new ActiveSubKey(multitap[i], layout, displayLayer);
}
}


if(key.flick) {
analysisFlagObj.hasFlicks = true;
for(let flickKey in key.flick) {
ActiveSubKey.polyfill(key.flick[flickKey as keyof TouchLayoutFlick], keyboard, layout, displayLayer, analysisFlagObj);
const flick = this.flick;
if(flick) {
for(let flickKey in flick) {
flick[flickKey] = new ActiveSubKey(flick[flickKey], layout, displayLayer);
}
}

let aKey = key as ActiveKey;
aKey.displayLayer = displayLayer;
aKey.layer = aKey.layer || displayLayer;

ActiveKeyBase.determineHint(aKey, layout.defaultHint);

// Compute the key's base KeyEvent properties for use in future event generation
// It's actually somewhat expensive to do this at the start, so we do a lazy-init.
aKey._baseKeyEvent = () => aKey.constructBaseKeyEvent(layout, displayLayer);
ActiveKey.determineHint(this, layout.defaultHint);
}

private static determineHint(spec: ActiveKey, defaultHint: TouchLayout.TouchLayoutDefaultHint): void {
Expand Down Expand Up @@ -433,73 +487,6 @@ export class ActiveKeyBase {
return;
}
}

@Enumerable
private constructBaseKeyEvent(layout: ActiveLayout, displayLayer: string) {
// Get key name and keyboard shift state (needed only for default layouts and physical keyboard handling)
// Note - virtual keys should be treated case-insensitive, so we force uppercasing here.
let layer = this.layer || displayLayer || '';
let keyName= this.id ? this.id.toUpperCase() : null;

// Start: mirrors _GetKeyEventProperties

// First check the virtual key, and process shift, control, alt or function keys
let props: KeyEventSpec = {
// Override key shift state if specified for key in layout (corrected for popup keys KMEW-93)
Lmodifiers: Codes.getModifierState(layer),
Lstates: Codes.getStateFromLayer(layer),
Lcode: keyName ? Codes.keyCodes[keyName] : 0,
LisVirtualKey: true,
vkCode: 0,
kName: keyName,
kLayer: layer,
kbdLayer: displayLayer,
kNextLayer: this.nextlayer,
device: null,
isSynthetic: true
};

let Lkc: KeyEvent = new KeyEvent(props);

if(layout.keyboard) {
let keyboard = layout.keyboard;

// Include *limited* support for mnemonic keyboards (Sept 2012)
// If a touch layout has been defined for a mnemonic keyout, do not perform mnemonic mapping for rules on touch devices.
if(keyboard.isMnemonic && !(layout.isDefault && layout.formFactor != 'desktop')) {
if(Lkc.Lcode != Codes.keyCodes['K_SPACE']) { // exception required, March 2013
// Jan 2019 - interesting that 'K_SPACE' also affects the caps-state check...
Lkc.vkCode = Lkc.Lcode;
this.isMnemonic = true;
}
} else {
Lkc.vkCode=Lkc.Lcode;
}

// Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic
if(!keyboard.definesPositionalOrMnemonic) {
Lkc.Lcode = KeyMapping._USKeyCodeToCharCode(Lkc);
Lkc.LisVirtualKey=false;
}
}

return Lkc;
}
}


export class ActiveKey extends ActiveKeyBase implements LayoutKey {
public getSubkey(coreID: string): ActiveKey {
if(this.sk) {
for(let key of this.sk) {
if(key.coreID == coreID) {
return key;
}
}
}

return null;
}
}


Expand Down Expand Up @@ -574,7 +561,18 @@ export class ActiveRow implements LayoutRow {
break;
}

ActiveKey.polyfill(key, keyboard, layout, displayLayer, analysisFlagObj);
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
Expand Down Expand Up @@ -889,8 +887,8 @@ export class ActiveLayout implements LayoutFormFactor{
defaultShift.multitap = [{...Layouts.dfltShiftToCaps}, {...Layouts.dfltShiftToDefault}] as ActiveSubKey[];
shiftShift.multitap = [{...Layouts.dfltShiftToCaps}, {...Layouts.dfltShiftToShift}] as ActiveSubKey[];

defaultShift.multitap.forEach((sk) => ActiveSubKey.polyfill(sk, keyboard, aLayout, 'default'));
shiftShift .multitap.forEach((sk) => ActiveSubKey.polyfill(sk, keyboard, aLayout, 'shift'));
defaultShift.multitap.forEach((sk, index) => defaultShift[index] = new ActiveSubKey(sk, aLayout, 'default'));
shiftShift .multitap.forEach((sk, index) => shiftShift[index] = new ActiveSubKey(sk, aLayout, 'shift'));
} // else no default shift -> caps multitaps.
}

Expand Down
1 change: 0 additions & 1 deletion web/src/engine/osk/src/input/gestures/browser/oskSubKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export default class OSKSubKey extends OSKKey {
let spec = this.spec;

let kDiv=document.createElement('div');
let tKey = osk.getDefaultKeyObject();
let ks=kDiv.style;

kDiv.className='kmw-key-square-ex';
Expand Down
4 changes: 0 additions & 4 deletions web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ export default class OSKLayerGroup {

layers=layout['layer'];

// Set key default attributes (must use exportable names!)
var tKey=vkbd.getDefaultKeyObject();
tKey['fontsize']=ls.fontSize;

for(n=0; n<layers.length; n++) {
let layer=layers[n] as ActiveLayer;
const layerObj = new OSKLayer(vkbd, layout, layer);
Expand Down
12 changes: 0 additions & 12 deletions web/src/engine/osk/src/visualKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,18 +902,6 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
}
}
}

/**
* Returns the default properties for a key object, used to construct
* both a base keyboard key and popup keys
*
* @return {Object} An object that contains default key properties
*/
getDefaultKeyObject(): ActiveKey {
const baseKeyObject: LayoutKey = {...ActiveKey.DEFAULT_KEY};
ActiveKey.polyfill(baseKeyObject, this.layoutKeyboard, this.kbdLayout, this.layerId);
return baseKeyObject as ActiveKey;
};
//#endregion

//#region OSK touch handlers
Expand Down

0 comments on commit b9cfaa1

Please sign in to comment.