refactor(web): partial conversion for inner layout calcs

This commit is contained in:
jahorton 2021-07-13 15:38:49 +07:00
parent bbedb11e72
commit acecff3679
7 changed files with 162 additions and 70 deletions

View file

@ -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;
}
}
}
}

View file

@ -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);
}
}

View file

@ -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<nRows; nRow++) {
let bottom = (nRows-nRow-1)*rowHeight+1;
// Calculate the exact vertical coordinate of the row's center.
this.spec.row[nRow].proportionalY = ((paddedHeight - bottom) - rowHeight/2) / paddedHeight;
if(vkbd.device.touchable) {
// Calculate the exact vertical coordinate of the row's center.
this.spec.row[nRow].proportionalY = ((paddedHeight - bottom) - rowHeight/2) / paddedHeight;
}
const oskRow = this.rows[nRow];
oskRow.refreshLayout(vkbd, rowHeight, bottom, rowPad);

View file

@ -233,7 +233,7 @@ namespace com.keyman.osk {
private get width(): number {
if(this.vkbd) {
return this.vkbd.width;
return this.vkbd.computedWidth;
} else {
return this._baseWidth;
}
@ -241,7 +241,7 @@ namespace com.keyman.osk {
private get height(): number {
if(this.vkbd) {
return this.vkbd.height;
return this.vkbd.computedHeight;
} else {
return this._baseHeight;
}

View file

@ -69,27 +69,31 @@ namespace com.keyman.osk {
public refreshLayout(vkbd: VisualKeyboard, rowHeight: number, bottom: number, pad: number) {
const rs = this.element.style;
if(!vkbd.isStatic) {
rs.bottom=bottom+'px';
if(vkbd.device.touchable) {
if(!vkbd.isStatic) {
rs.bottom=bottom+'px';
}
rs.maxHeight=rs.lineHeight=rs.height=rowHeight+'px';
}
rs.maxHeight=rs.lineHeight=rs.height=rowHeight+'px';
for(const key of this.keys) {
const keySquare = key.btn.parentElement;
const keyElement = key.btn;
// Set the kmw-key-square position
const kss = keySquare.style;
if(!vkbd.isStatic) {
kss.bottom=(bottom-pad/2)+'px';
}
kss.height=kss.minHeight=(rowHeight)+'px';
if(vkbd.device.touchable) {
// Set the kmw-key-square position
const kss = keySquare.style;
if(!vkbd.isStatic) {
kss.bottom=(bottom-pad/2)+'px';
}
kss.height=kss.minHeight=(rowHeight)+'px';
const kes = keyElement.style;
if(!vkbd.isStatic) {
kes.bottom=bottom+'px';
const kes = keyElement.style;
if(!vkbd.isStatic) {
kes.bottom=bottom+'px';
}
kes.height=kes.lineHeight=kes.minHeight=(rowHeight-pad)+'px';
}
kes.height=kes.lineHeight=kes.minHeight=(rowHeight-pad)+'px';
if(keyElement.key) {
keyElement.key.refreshLayout(vkbd);

View file

@ -1,6 +1,7 @@
/// <reference path="lengthStyle.ts" />
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);
}
}
}

View file

@ -1,4 +1,5 @@
/// <reference path="preProcessor.ts" />
/// <reference path="lengthStyle.ts" />
/// <reference path="utils.ts" />
/// <reference path="oskLayerGroup.ts" />
/// <reference path="keytip.interface.ts" />
@ -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 {