mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-15 21:29:23 +00:00
refactor(web): partial conversion for inner layout calcs
This commit is contained in:
parent
bbedb11e72
commit
acecff3679
7 changed files with 162 additions and 70 deletions
75
web/source/osk/lengthStyle.ts
Normal file
75
web/source/osk/lengthStyle.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue