Merge pull request #9829 from keymanapp/feat/web/custom-key-hints

feat(web): configurable key-cap hints 🐵
This commit is contained in:
Joshua Horton 2023-11-06 08:27:28 +07:00 committed by GitHub
commit 1bfe774ef1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 111 additions and 11 deletions

View file

@ -57,7 +57,9 @@ const KeyTypesOfKeyMap = {
default: 'boolean'
} as const;
const KeyTypesOfFlickList = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'] as const;
// Keep in this specific order: it's the ordering of priority for default hint selection when
// based on available hints. (i.e., `layout.defaultHint == 'flick'`)
const KeyTypesOfFlickList = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'] as const;
export class ActiveKeyBase {
static readonly DEFAULT_PAD=15; // Padding to left of key, in virtual units
@ -75,6 +77,11 @@ export class ActiveKeyBase {
/** WARNING - DO NOT USE DIRECTLY outside of @keymanapp/keyboard-processor! */
id: TouchLayout.TouchLayoutKeyId;
text: string;
hint?: string;
hintSrc?: TouchLayout.TouchLayoutSubKey | TouchLayout.TouchLayoutKey;
font?: string;
fontsize?: string;
// These are fine.
width?: number;
@ -348,10 +355,66 @@ export class ActiveKeyBase {
aKey.displayLayer = displayLayer;
aKey.layer = aKey.layer || displayLayer;
ActiveKeyBase.determineHint(aKey, layout.defaultHint);
// Compute the key's base KeyEvent properties for use in future event generation
aKey.constructBaseKeyEvent(keyboard, layout, displayLayer);
}
private static determineHint(spec: ActiveKey, defaultHint: TouchLayout.TouchLayoutDefaultHint): void {
// If a hint was directly specified, don't override it.
if(spec.hint) {
spec.hintSrc = spec;
return;
}
// Is more compact than writing 8 separate cases.
if(defaultHint?.includes('flick-')) {
if(spec.flick) {
// 6 = length of 'flick-'
const dir = defaultHint.substring(6);
if(spec.flick[dir]?.text) {
spec.hintSrc = spec.flick[dir];
}
}
return;
}
switch(defaultHint) {
case 'none':
return;
case 'multitap':
if(spec.multitap) {
spec.hintSrc = spec.multitap[0];
}
return;
case 'flick':
if(spec.flick) {
for(const key of KeyTypesOfFlickList) {
if(spec.flick[key]) {
spec.hintSrc = spec.flick[key];
return;
}
}
}
return;
case 'longpress':
if(spec.sk) {
spec.hintSrc = spec.sk[0];
}
return;
case 'dot':
default:
if(spec.sk) {
spec.hint = '\u2022';
spec.hintSrc = spec;
}
return;
}
}
private constructBaseKeyEvent(keyboard: Keyboard, layout: ActiveLayout, displayLayer: string) {
// Get key name and keyboard shift state (needed only for default layouts and physical keyboard handling)
// Note - virtual keys should be treated case-insensitive, so we force uppercasing here.

View file

@ -5,6 +5,7 @@ import OSKKey from './oskKey.js';
import { KeyData, KeyElement, link } from '../keyElement.js';
import OSKRow from './oskRow.js';
import VisualKeyboard from '../visualKeyboard.js';
import { ParsedLengthStyle } from '../lengthStyle.js';
export default class OSKBaseKey extends OSKKey {
@ -81,12 +82,6 @@ export default class OSKBaseKey extends OSKKey {
bsk[bsn].layer = btn.key.layer
}
}
// If a subkey array is defined, add an icon
var skIcon = document.createElement('div');
skIcon.className='kmw-key-popup-icon';
//kDiv.appendChild(skIcon);
btn.appendChild(skIcon);
}
construct(vkbd: VisualKeyboard): HTMLDivElement {
@ -120,6 +115,10 @@ export default class OSKBaseKey extends OSKKey {
btn['subKeys']=null;
}
// If a subkey array is defined, add an icon
const skIcon = this.generateHint();
btn.appendChild(skIcon);
// Add text to button and button to placeholder div
kDiv.appendChild(btn);
@ -127,6 +126,43 @@ export default class OSKBaseKey extends OSKKey {
return this.square = kDiv;
}
public generateHint(): HTMLDivElement {
// If a hint is defined, add an icon
const skIcon = document.createElement('div');
// Ensure that we use the keyboard's text font for hints.
skIcon.className='kmw-key-popup-icon';
const hintSpec = this.spec.hintSrc;
if(!hintSpec) {
return skIcon;
}
if(hintSpec.font) {
skIcon.style.fontFamily = hintSpec.font;
} else {
skIcon.classList.add('kmw-key-text');
}
if(hintSpec.fontsize) {
const parsed = new ParsedLengthStyle(hintSpec.fontsize);
// From kmwosk.css: .kmw-key-popup-icon { font-size: 0.5em }
// The spec says to overwrite that, but we still want half-size compared to the text
// as a key-cap.
skIcon.style.fontSize = parsed.scaledBy(0.5).styleString;
}
// If the base key itself is the source of the hint text, we use `hint` directly.
// Otherwise, we present the source subkey's key cap as the hint.
const text = hintSpec == this.spec ? this.spec.hint : hintSpec.text;
if(text == '\u2022') {
// The original, pre-17.0 longpress dot-hint used bold-face styling.
skIcon.style.fontWeight='bold';
}
skIcon.textContent = text;
return skIcon;
}
public refreshLayout(vkbd: VisualKeyboard) {
let key = this.spec as ActiveKey;
this.square.style.width = vkbd.layoutWidth.scaledBy(key.proportionalWidth).styleString;

View file

@ -196,7 +196,9 @@
.tablet .kmw-key {border-radius:8px;overflow:visible;border:solid 2px #999999;box-shadow: 0px -1px 1px 0px rgba(0,0,0,1) inset;}
.tablet .kmw-key-label{position:absolute;left:7%;top:2%;font:0.5em Arial;color:#aaa;background-color:transparent;z-index:10000;}
.tablet .kmw-key-text{position:relative;left:0;top:0;-webkit-user-select:none;}
.tablet .kmw-key-text {
-webkit-user-select:none;
}
.tablet.ios .kmw-key-layer-group {background-color: #cfd3d9}
.tablet.ios .kmw-key {border: none; border-bottom: solid 1px #8a8d90; box-shadow:none; border-radius: 5px;}
@ -271,10 +273,9 @@
.desktop .kmw-key-label{position:absolute;left:2px;top:2px;font:0.5em Arial;color:#888;background-color:transparent;}
/* Popup icon style (and content)*/
.kmw-key-popup-icon:before{content:'\2022';}
.kmw-key-popup-icon{position:absolute;display:block;visibility:visible;right:4%;top:1%;/*width:8px;height:8px;*/
font:bold 0.5em Arial;color:#aaa;}
font-size:0.5em;color:#aaa; line-height:initial}
/*.phone .kmw-key-popup-icon{right:6%;top:-2px;width:8px;height:8px;text-align:right;}
.desktop .kmw-key-popup-icon{right:4%;top:0;width:8px;height:8px;} */