spiegel-keyman/web/source/osk/browser/keytip.ts
2022-03-24 07:35:28 +11:00

145 lines
No EOL
5.2 KiB
TypeScript

namespace com.keyman.osk.browser {
export class KeyTip implements com.keyman.osk.KeyTip {
public readonly element: HTMLDivElement;
public key: KeyElement;
public state: boolean = false;
// -----
// | | <-- tip
// | x | <-- label
// |_ _|
// | |
// | | <-- cap
// |___|
private readonly cap: HTMLDivElement;
private readonly tip: HTMLDivElement;
private readonly label: HTMLSpanElement;
private readonly constrain: boolean;
/**
*
* @param constrain keep the keytip within the bounds of the overall OSK.
* Will probably be handled via function in a later pass.
*/
constructor(constrain: boolean) {
let tipElement = this.element=document.createElement('div');
tipElement.className='kmw-keytip';
tipElement.id = 'kmw-keytip';
// The following style is critical, so do not rely on external CSS
tipElement.style.pointerEvents='none';
tipElement.style.display='none';
tipElement.appendChild(this.tip = document.createElement('div'));
tipElement.appendChild(this.cap = document.createElement('div'));
this.tip.appendChild(this.label = document.createElement('span'));
this.tip.className = 'kmw-keytip-tip';
this.cap.className = 'kmw-keytip-cap';
this.label.className = 'kmw-keytip-label';
this.constrain = constrain;
}
show(key: KeyElement, on: boolean, vkbd: VisualKeyboard) {
let keyman = com.keyman.singleton;
let util = keyman.util;
// Create and display the preview
// If !key.offsetParent, the OSK is probably hidden. Either way, it's a half-
// decent null-guard check.
if(on && key.offsetParent) {
// The key element is positioned relative to its key-square, which is,
// in turn, relative to its row. Rows take 100% width, so this is sufficient.
//
let rowElement = (key.key as OSKBaseKey).row.element;
// May need adjustment for borders if ever enabled for the desktop form-factor target.
let rkey = key.getClientRects()[0], rrow = rowElement.getClientRects()[0];
let xLeft = rkey.left - rrow.left,
xWidth = rkey.width,
xHeight = rkey.height,
kc = key.key.label,
previewFontScale = 1.8;
let kts = this.element.style;
// Roughly matches how the subkey positioning is set.
const _Box = vkbd.element.parentNode as HTMLDivElement;
const _BoxRect = _Box.getBoundingClientRect();
const keyRect = key.getBoundingClientRect();
let y = (keyRect.bottom - _BoxRect.top + 1);
let ySubPixelPadding = y - Math.floor(y);
// Canvas dimensions must be set explicitly to prevent clipping
// This gives us exactly the same number of pixels on left and right
let canvasWidth = xWidth + Math.ceil(xWidth * 0.3) * 2;
let canvasHeight = Math.ceil(2.3 * xHeight) + (ySubPixelPadding); //
kts.top = Math.floor(y - canvasHeight) + 'px';
kts.textAlign = 'center';
kts.overflow = 'visible';
kts.fontFamily = util.getStyleValue(kc,'font-family');
kts.width = canvasWidth+'px';
kts.height = canvasHeight+'px';
var px=util.getStyleInt(kc, 'font-size');
if(px != 0) {
let popupFS = previewFontScale * px;
let scaleStyle = {
fontFamily: kts.fontFamily,
fontSize: popupFS + 'px',
height: 1.6 * xHeight + 'px' // as opposed to the canvas height of 2.3 * xHeight.
};
kts.fontSize = key.key.getIdealFontSize(vkbd, scaleStyle, true);
}
this.label.textContent = kc.textContent;
// Adjust shape if at edges
var xOverflow = (canvasWidth - xWidth) / 2;
if(xLeft < xOverflow) {
this.cap.style.left = '1px';
xLeft += xOverflow - 1;
} else if(xLeft > window.innerWidth - xWidth - xOverflow) {
this.cap.style.left = (canvasWidth - xWidth - 1) + 'px';
xLeft -= xOverflow - 1;
} else {
this.cap.style.left = xOverflow + 'px';
}
kts.left=(xLeft - xOverflow) + 'px';
let cs = getComputedStyle(this.element);
let oskHeight = keyman.osk.computedHeight;
let bottomY = parseFloat(cs.bottom);
let tipHeight = parseFloat(cs.height);
let halfHeight = Math.ceil(canvasHeight / 2);
this.cap.style.width = xWidth + 'px';
this.tip.style.height = halfHeight + 'px';
this.cap.style.top = (halfHeight - 3) + 'px';
this.cap.style.height = (keyRect.bottom - _BoxRect.top - Math.floor(y - canvasHeight) - (halfHeight)) + 'px'; //(halfHeight + 3 + ySubPixelPadding) + 'px';
if(this.constrain && tipHeight + bottomY > oskHeight) {
const delta = tipHeight + bottomY - oskHeight;
kts.height = (canvasHeight-delta) + 'px';
const hx = Math.max(0, (canvasHeight-delta)-(canvasHeight/2) + 2);
this.cap.style.height = hx + 'px';
}
kts.display = 'block';
} else { // Hide the key preview
this.element.style.display = 'none';
}
// Save the key preview state
this.key = key;
this.state = on;
}
}
}