From acecff3679ba0df4f212d2e84f5d082c10efccfe Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 13 Jul 2021 15:38:49 +0700 Subject: [PATCH] refactor(web): partial conversion for inner layout calcs --- web/source/osk/lengthStyle.ts | 75 ++++++++++++++++++++++++++++++++ web/source/osk/oskKey.ts | 2 +- web/source/osk/oskLayer.ts | 19 +++++--- web/source/osk/oskManager.ts | 4 +- web/source/osk/oskRow.ts | 30 +++++++------ web/source/osk/utils.ts | 27 +++--------- web/source/osk/visualKeyboard.ts | 75 +++++++++++++++++++++----------- 7 files changed, 162 insertions(+), 70 deletions(-) create mode 100644 web/source/osk/lengthStyle.ts diff --git a/web/source/osk/lengthStyle.ts b/web/source/osk/lengthStyle.ts new file mode 100644 index 0000000000..732b6f3745 --- /dev/null +++ b/web/source/osk/lengthStyle.ts @@ -0,0 +1,75 @@ +namespace com.keyman.osk { + export interface LengthStyle { + val: number, + absolute: boolean + }; + + export class ParsedLengthStyle implements LengthStyle { + public readonly val: number; + public readonly absolute: boolean; + + public constructor(style: LengthStyle | string) { + if(typeof style == 'string') { + const parsed = ParsedLengthStyle.parseLengthStyle(style); + this.val = parsed.val; + this.absolute = parsed.absolute; + } else { + this.val = style.val; + this.absolute = style.absolute; + } + } + + public get styleString(): string { + if(this.absolute) { + return this.val + 'px'; + } else { + return this.absolute + '%'; + } + } + + public scaledBy(scalar: number): ParsedLengthStyle { + return new ParsedLengthStyle({ + val: scalar * this.val, + absolute: this.absolute + }); + } + + public static inPixels(val: number): ParsedLengthStyle { + return new ParsedLengthStyle({val: val, absolute: true}); + } + + public static inPercent(val: number): ParsedLengthStyle { + return new ParsedLengthStyle({val: val/100, absolute: false}); + } + + public static forScalar(val: number): ParsedLengthStyle { + return new ParsedLengthStyle({val: val, absolute: false}); + } + + private static parseLengthStyle(spec: string): {val: number, absolute: boolean} { + var val: number; + + if(spec.indexOf('px') != -1) { + val = parseFloat(spec.substr(0, spec.indexOf('px'))); + return {val: val, absolute: true}; + } else if(spec.indexOf('pt') != -1) { + // 16 px ~= 12 pt. + // Reference: https://kyleschaeffer.com/css-font-size-em-vs-px-vs-pt-vs-percent + val = parseFloat(spec.substr(0, spec.indexOf('pt'))); + return {val: (4 * val / 3), absolute: true}; + } else if(spec.indexOf('%') != -1) { + val = parseFloat(spec.substr(0, spec.indexOf('%'))); + return {val: val/100, absolute: false}; + } else if(!isNaN(val = Number(spec))) { + // Note: this one is NOT natively handled by browsers! + // We'll treat it as if it were 'pt', since that's likely the user's + // most familiar font size unit. + return {val: (4 * val / 3), absolute: true}; + } else { + // Cannot parse. + console.error("Could not properly parse specified length style info: '" + spec + "'."); + return null; + } + } + } +} \ No newline at end of file diff --git a/web/source/osk/oskKey.ts b/web/source/osk/oskKey.ts index 71cad5132e..8efb933a12 100644 --- a/web/source/osk/oskKey.ts +++ b/web/source/osk/oskKey.ts @@ -346,7 +346,7 @@ namespace com.keyman.osk { // approximation for that. `this.kbdDiv` is the element controlling the OSK's width, set in px. // This is an approximation that tends to be a bit too large, but it's close enough to be useful. - return Math.floor(vkbd.width * this.spec['widthpc'] / 100); + return Math.floor(vkbd.computedWidth * this.spec['widthpc'] / 100); } } diff --git a/web/source/osk/oskLayer.ts b/web/source/osk/oskLayer.ts index fe21da8171..b81b44a9b0 100644 --- a/web/source/osk/oskLayer.ts +++ b/web/source/osk/oskLayer.ts @@ -90,13 +90,16 @@ namespace com.keyman.osk { public refreshLayout(vkbd: VisualKeyboard, paddedHeight: number, trueHeight: number) { // Check the heights of each row, in case different layers have different row counts. let nRows = this.rows.length; - this.element.style.height=(paddedHeight)+'px'; - let rowHeight = Math.floor(trueHeight/(nRows == 0 ? 1 : nRows)); - if(vkbd.device.OS == 'Android' && 'devicePixelRatio' in window) { - this.element.style.height = this.element.style.maxHeight = paddedHeight + 'px'; - rowHeight /= window.devicePixelRatio; + if(vkbd.device.touchable) { + this.element.style.height=(paddedHeight)+'px'; + + + if(vkbd.device.OS == 'Android' && 'devicePixelRatio' in window) { + this.element.style.height = this.element.style.maxHeight = paddedHeight + 'px'; + rowHeight /= window.devicePixelRatio; + } } // Sets the layers to the correct height @@ -105,8 +108,10 @@ namespace com.keyman.osk { for(let nRow=0; nRow + namespace com.keyman.osk { export function getFontSizeStyle(e: HTMLElement|string): {val: number, absolute: boolean} { - var val: number; var fs: string; if(typeof e == 'string') { @@ -13,28 +14,10 @@ namespace com.keyman.osk { } if(fs.indexOf('em') != -1) { - val = parseFloat(fs.substr(0, fs.indexOf('em'))); - return {val: val, absolute: false}; - } else if(fs.indexOf('px') != -1) { - val = parseFloat(fs.substr(0, fs.indexOf('px'))); - return {val: val, absolute: true}; - } else if(fs.indexOf('pt') != -1) { - // 16 px ~= 12 pt. - // Reference: https://kyleschaeffer.com/css-font-size-em-vs-px-vs-pt-vs-percent - val = parseFloat(fs.substr(0, fs.indexOf('pt'))); - return {val: (4 * val / 3), absolute: true}; - } else if(fs.indexOf('%') != -1) { - val = parseFloat(fs.substr(0, fs.indexOf('%'))); - return {val: val/100, absolute: false}; - } else if(!isNaN(val = Number(fs))) { - // Note: this one is NOT natively handled by browsers! - // We'll treat it as if it were 'pt', since that's likely the user's - // most familiar font size unit. - return {val: (4 * val / 3), absolute: true}; + const val = parseFloat(fs); + return ParsedLengthStyle.forScalar(val); } else { - // Cannot parse. - console.error("Could not properly parse specified fontsize info: '" + fs + "'."); - return null; + return new ParsedLengthStyle(fs); } } } \ No newline at end of file diff --git a/web/source/osk/visualKeyboard.ts b/web/source/osk/visualKeyboard.ts index 30073460f5..988b4f5342 100644 --- a/web/source/osk/visualKeyboard.ts +++ b/web/source/osk/visualKeyboard.ts @@ -1,4 +1,5 @@ /// +/// /// /// /// @@ -41,13 +42,13 @@ namespace com.keyman.osk { * The configured width for this VisualKeyboard. May be `undefined` or `null` * to allow automatic width scaling. */ - private _width: number; + private _width: ParsedLengthStyle; /** * The configured height for this VisualKeyboard. May be `undefined` or `null` * to allow automatic height scaling. */ - private _height: number; + private _height: ParsedLengthStyle; /** * The computed width for this VisualKeyboard. May be null if auto sizing @@ -205,7 +206,7 @@ namespace com.keyman.osk { * The configured width for this VisualKeyboard. May be `undefined` or `null` * to allow automatic width scaling. */ - get width(): number { + get width(): LengthStyle { return this._width; } @@ -213,7 +214,7 @@ namespace com.keyman.osk { * The configured height for this VisualKeyboard. May be `undefined` or `null` * to allow automatic height scaling. */ - get height(): number { + get height(): LengthStyle { return this._height; } @@ -222,6 +223,12 @@ namespace com.keyman.osk { * is allowed and the VisualKeyboard is not currently in the DOM hierarchy. */ get computedWidth(): number { + if(!this.kbdDiv) { + // Intermediate state - can be called during VisualKeyboard's constructor, before + // computedHeight can receive a value. + return undefined; + } + // Computed during layout operations; allows caching instead of continuous recomputation. if(this.needsLayout) { let osk = com.keyman.singleton.osk; @@ -235,6 +242,12 @@ namespace com.keyman.osk { * is allowed and the VisualKeyboard is not currently in the DOM hierarchy. */ get computedHeight(): number { + if(!this.kbdDiv) { + // Intermediate state - can be called during VisualKeyboard's constructor, before + // computedHeight can receive a value. + return undefined; + } + // Computed during layout operations; allows caching instead of continuous recomputation. if(this.needsLayout) { let osk = com.keyman.singleton.osk; @@ -250,18 +263,27 @@ namespace com.keyman.osk { * @param pending Set to `true` if called during a resizing interaction */ public setSize(width?: number, height?: number, pending?: boolean) { - this._width = width; - this._height = height; + this._width = ParsedLengthStyle.inPixels(width); + this._height = ParsedLengthStyle.inPixels(height); if(!pending && this.kbdDiv) { - this.kbdDiv.style.width = width ? this._width+'px' : ''; - this.kbdDiv.style.height = height ? this._height+'px' : ''; - this.kbdDiv.style.fontSize = height ? (this._height/8)+'px' : ''; + this.kbdDiv.style.width = width ? this._width.styleString : ''; + this.kbdDiv.style.height = height ? this._height.styleString : ''; + this.kbdDiv.style.fontSize = height ? (this._height.scaledBy(1/8).styleString) : ''; + + let osk = com.keyman.singleton.osk; + this.refreshLayout(osk.getKeyboardHeight()); + } else { + this.needsLayout = true; } } + public setNeedsLayout(): void { + this.needsLayout = true; + } + public defaultFontSize(): number { - return this.height ? this.height / 8 : undefined; + return this.computedHeight ? this.computedHeight / 8 : undefined; } /** @@ -269,8 +291,8 @@ namespace com.keyman.osk { * size actually used by the visual keyboard. */ public refit() { - this._width=this.kbdDiv.offsetWidth; - this._height=this.kbdDiv.offsetHeight; + this._width = ParsedLengthStyle.inPixels(this.kbdDiv.offsetWidth); + this._height = ParsedLengthStyle.inPixels(this.kbdDiv.offsetHeight); } /** @@ -550,7 +572,6 @@ namespace com.keyman.osk { * **/ moveOver: (e: TouchEvent) => void = function(this: VisualKeyboard, e: TouchEvent) { - let keyman = com.keyman.singleton; e.preventDefault(); e.cancelBubble=true; @@ -603,14 +624,13 @@ namespace com.keyman.osk { this.currentTarget = key1; // _Box has (most of) the useful client values. - let _Box = this.kbdDiv.parentElement ? this.kbdDiv.parentElement : keyman.osk._Box; let height = this.kbdDiv.offsetHeight; // We need to adjust the offset properties by any offsets related to the active banner. // Determine the y-threshold at which touch-cancellation should automatically occur. let rowCount = this.currentLayer.rows.length; let yBufferThreshold = (0.333 * height / rowCount); // Allows vertical movement by 1/3 the height of a row. - var yMin = (this.kbdDiv && _Box) ? Math.max(5, this.kbdDiv.offsetTop - yBufferThreshold) : 5; + var yMin = Math.max(5, this.kbdDiv.offsetTop - yBufferThreshold); if(key0 && e.touches[0].pageY < yMin) { this.highlightKey(key0,false); this.showKeyTip(null,false); @@ -1097,26 +1117,23 @@ namespace com.keyman.osk { let b = this.kbdDiv.firstChild as HTMLElement; let gs = this.kbdDiv.style; let bs=b.style; - // Sets the layer group to the correct height. - gs.height=gs.maxHeight=paddedHeight+'px'; - bs.fontSize=fs+'em'; - - for(const layerId in this.layerGroup.layers) { - const layer = this.layerGroup.layers[layerId]; - layer.refreshLayout(this, paddedHeight, height); + if(device.touchable) { + // Sets the layer group to the correct height. + gs.height = gs.maxHeight = paddedHeight + 'px'; } + bs.fontSize=fs+'em'; // NEW CODE ------ // Step 1: have the necessary conditions been met? - const fixedSize = this.width && this.height; + const fixedSize = this.width && this.height && this.width.absolute && this.height.absolute; const computedStyle = getComputedStyle(this.kbdDiv); const isInDOM = computedStyle.height != '' && computedStyle.height != 'auto'; // Step 2: determine basic layout geometry if(fixedSize) { - this._computedWidth = this.width; - this._computedHeight = this.height; + this._computedWidth = this.width.val; + this._computedHeight = this.height.val; } else if(isInDOM) { this._computedWidth = parseInt(computedStyle.width, 10); if(!this._computedWidth) { @@ -1138,6 +1155,14 @@ namespace com.keyman.osk { // - rescale key text this.needsLayout = false; + + // END NEW CODE ----------- + + // Needs the refreshed layout info to work correctly. + for(const layerId in this.layerGroup.layers) { + const layer = this.layerGroup.layers[layerId]; + layer.refreshLayout(this, paddedHeight, height); + } } /*private*/ computedAdjustedOskHeight(allottedHeight: number): number {