mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 17:05:34 +00:00
145 lines
No EOL
5.2 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
} |