diff --git a/web/src/engine/main/src/keymanEngine.ts b/web/src/engine/main/src/keymanEngine.ts index 1400c82e9c..b7a593d3f8 100644 --- a/web/src/engine/main/src/keymanEngine.ts +++ b/web/src/engine/main/src/keymanEngine.ts @@ -152,16 +152,32 @@ export default class KeymanEngine< this.osk.startHide(false); } - if(this.osk) { - this.osk.setNeedsLayout(); - this.osk.activeKeyboard = kbd; - this.osk.present(); - } - // Needed to ensure the correct layer is displayed. // Needs to be after the OSK has loaded for the keyboard in case the default // layer should be something other than "default" for the current context. - this.core.resetContext(this.contextManager.activeTarget); + const doContextReset = () => { + this.contextManager.resetContext(); + } + + /* + This pattern is designed to minimize layout reflow during the keyboard-swap process. + The 'default' layer is loaded by default, but some keyboards will start on different + layers depending on the current state of the context. + + If possible, we want to only perform layout operations once the correct layer is + set to active. + */ + if(this.osk) { + this.osk.batchLayoutAfter(() => { + this.osk.activeKeyboard = kbd; + // Note: when embedded within the mobile apps, the keyboard will still be visible + // at this time. + doContextReset(); + this.osk.present(); + }); + } else { + doContextReset(); + } }); this.contextManager.on('keyboardasyncload', (metadata) => { @@ -216,7 +232,15 @@ export default class KeymanEngine< resetContext: (target) => { // Could reset the target's deadkeys here, but it's really more of a 'core' task. // So we delegate that to keyboard-processor. - this.core.resetContext(target); + const doReset = () => this.core.resetContext(target); + + if(this.osk) { + this.osk.batchLayoutAfter(() => { + doReset(); + }) + } else { + doReset(); + } }, predictionContext: new PredictionContext(this.core.languageProcessor, this.core.keyboardProcessor), keyboardCache: this.keyboardRequisitioner.cache diff --git a/web/src/engine/osk/src/views/oskView.ts b/web/src/engine/osk/src/views/oskView.ts index 85b63067ab..2b67d3d165 100644 --- a/web/src/engine/osk/src/views/oskView.ts +++ b/web/src/engine/osk/src/views/oskView.ts @@ -159,6 +159,7 @@ export default abstract class OSKView private uiStyleSheetManager: StylesheetManager; private config: Configuration; + private deferLayout: boolean; private _boxBaseMouseDown: (e: MouseEvent) => boolean; private _boxBaseTouchStart: (e: TouchEvent) => boolean; @@ -595,8 +596,24 @@ export default abstract class OSKView this.needsLayout = true; } + public batchLayoutAfter(closure: () => void) { + try { + this.deferLayout = true; + if(this.vkbd) { + this.vkbd.deferLayout = true; + } + closure(); + } finally { + this.deferLayout = false; + if(this.vkbd) { + this.vkbd.deferLayout = false; + } + this.refreshLayout(); + } + } + public refreshLayout(pending?: boolean): void { - if(!this.keyboardView) { + if(!this.keyboardView || this.deferLayout) { return; } diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 34d267805c..67ae9f48fc 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -231,11 +231,20 @@ export default class VisualKeyboard extends EventEmitter implements Ke activeGestures: GestureHandler[] = []; activeModipress: Modipress = null; + private _deferLayout: boolean; // The keyboard object corresponding to this VisualKeyboard. public readonly layoutKeyboard: Keyboard; public readonly layoutKeyboardProperties: KeyboardProperties; + get deferLayout(): boolean { + return this._deferLayout; + } + + set deferLayout(value: boolean) { + this._deferLayout = value; + } + get layerId(): string { return this.layerGroup?.activeLayerId ?? 'default'; } @@ -253,7 +262,7 @@ export default class VisualKeyboard extends EventEmitter implements Ke } } - if(changedLayer) { + if(changedLayer && !this._deferLayout) { this.updateState(); // We changed the active layer, but not any layout property of the keyboard as a whole. this.layerGroup.refreshLayout(this.constructLayoutParams()); @@ -1206,6 +1215,10 @@ export default class VisualKeyboard extends EventEmitter implements Ke * when needed. */ refreshLayout() { + if(this._deferLayout) { + return; + } + /* Phase 1: calculations possible at the start without triggering _any_ additional layout reflow. (A single, initial reflow may happen depending on DOM manipulations before this method...,