mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-14 11:37:43 +00:00
change(web): floating tablet key-previews, hosted by layer-independent element
This commit is contained in:
parent
977ad9cfbf
commit
4f19b4cd1e
5 changed files with 162 additions and 7 deletions
|
|
@ -5,7 +5,7 @@ import VisualKeyboard from '../../../visualKeyboard.js';
|
|||
import { GesturePreviewHost } from '../../../keyboard-layout/gesturePreviewHost.js';
|
||||
|
||||
const CSS_PREFIX = 'kmw-';
|
||||
const DEFAULT_TIP_ORIENTATION = 'top';
|
||||
const DEFAULT_TIP_ORIENTATION: PhoneKeyTipOrientation = 'top';
|
||||
|
||||
export type PhoneKeyTipOrientation = 'top' | 'bottom';
|
||||
|
||||
|
|
@ -127,8 +127,13 @@ export default class KeyTip implements KeyTipInterface {
|
|||
kts.width = canvasWidth+'px';
|
||||
kts.height = canvasHeight+'px';
|
||||
|
||||
// Some keyboards (such as `balochi_scientific`) do not _package_ a font but
|
||||
// specify an extremely common one, such as Arial. In such cases, .kmw-key-text
|
||||
// custom styling doesn't exist, relying on the layer object to simply specify
|
||||
// the font-family.
|
||||
const layerFontFamily = this.vkbd.currentLayer.element.style.fontFamily;
|
||||
const ckts = getComputedStyle(vkbd.element);
|
||||
kts.fontFamily = ckts.fontFamily;
|
||||
kts.fontFamily = key.key.spec.font || layerFontFamily || ckts.fontFamily;
|
||||
|
||||
var px=parseInt(ckts.fontSize,10);
|
||||
if(px == Number.NaN) {
|
||||
|
|
|
|||
114
web/src/engine/osk/src/input/gestures/browser/tabletPreview.ts
Normal file
114
web/src/engine/osk/src/input/gestures/browser/tabletPreview.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import { KeyElement } from '../../../keyElement.js';
|
||||
import KeyTipInterface from '../../../keytip.interface.js';
|
||||
import VisualKeyboard from '../../../visualKeyboard.js';
|
||||
import { GesturePreviewHost } from '../../../keyboard-layout/gesturePreviewHost.js';
|
||||
|
||||
const BASE_CLASS = 'kmw-keypreview';
|
||||
const OVERLAY_CLASS = 'kmw-preview-overlay';
|
||||
const BASE_ID = 'kmw-keytip';
|
||||
|
||||
export class TabletKeyTip implements KeyTipInterface {
|
||||
public readonly element: HTMLDivElement;
|
||||
public key: KeyElement;
|
||||
public state: boolean = false;
|
||||
|
||||
private previewHost: GesturePreviewHost;
|
||||
private preview: HTMLDivElement;
|
||||
private readonly vkbd: VisualKeyboard;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param constrain keep the keytip within the bounds of the overall OSK.
|
||||
* Will probably be handled via function in a later pass.
|
||||
*/
|
||||
constructor(vkbd: VisualKeyboard) {
|
||||
this.vkbd = vkbd;
|
||||
const base = this.element=document.createElement('div');
|
||||
base.className=BASE_CLASS;
|
||||
base.id = 'kmw-keytip';
|
||||
|
||||
// The following style is critical, so do not rely on external CSS
|
||||
base.style.pointerEvents='none';
|
||||
base.style.display='none';
|
||||
|
||||
this.preview = document.createElement('div');
|
||||
base.appendChild(this.preview);
|
||||
}
|
||||
|
||||
show(key: KeyElement, on: boolean, previewHost: GesturePreviewHost) {
|
||||
const vkbd = this.vkbd;
|
||||
const keyLayer = key?.key.spec.displayLayer;
|
||||
|
||||
// During quick input sequences - especially during a multitap-modipress - it's possible
|
||||
// for a user to request a preview for a key from a layer that is currently active, but
|
||||
// currently not visible due to need previously-requested layout calcs for a different layer.
|
||||
if(on) {
|
||||
// Necessary for `key.offsetParent` and client-rect methods referenced below.
|
||||
// Will not unnecessarily force reflow if the layer is already in proper document flow,
|
||||
// but otherwise restores it.
|
||||
vkbd.layerGroup.blinkLayer(keyLayer);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// May need adjustment for borders if ever enabled for the desktop form-factor target.
|
||||
// const _Box = this.vkbd.topContainer;
|
||||
const hostRect = this.vkbd.element.getBoundingClientRect();
|
||||
const keyRect = key.getBoundingClientRect();
|
||||
|
||||
// Used to apply box-shadow overlay styling when the preview is for a key on a layer not
|
||||
// currently active. This is done in case the layers don't have perfect alignment for
|
||||
// all keys.
|
||||
const conditionalOverlayStyle = (keyLayer != vkbd.layerId) ? OVERLAY_CLASS : '';
|
||||
this.element.className = `${BASE_CLASS} ${key.className} ${conditionalOverlayStyle}`;
|
||||
|
||||
// Some keyboards use custom CSS styling based on partial-matching the key ID
|
||||
// (like sil_cameroon_azerty); this lets us map the custom styles onto the tablet
|
||||
// preview, too.
|
||||
this.element.id = `${BASE_ID}-${key.id}`;
|
||||
|
||||
const kts = this.element.style;
|
||||
|
||||
// Some keyboards (such as `balochi_scientific`) do not _package_ a font but
|
||||
// specify an extremely common one, such as Arial. In such cases, .kmw-key-text
|
||||
// custom styling doesn't exist, relying on the layer object to simply specify
|
||||
// the font-family.
|
||||
const fontFamily = this.vkbd.currentLayer.element.style.fontFamily;
|
||||
kts.fontFamily = key.key.spec.font || fontFamily;
|
||||
|
||||
kts.left = (keyRect.left - hostRect.left) + 'px';
|
||||
kts.top = (keyRect.top - hostRect.top) + 'px';
|
||||
kts.width = keyRect.width + 'px';
|
||||
kts.height = keyRect.height + 'px';
|
||||
|
||||
this.element.style.display = 'block';
|
||||
|
||||
if(this.previewHost == previewHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHost = this.preview;
|
||||
this.previewHost = previewHost;
|
||||
|
||||
if(previewHost) {
|
||||
this.preview = this.previewHost.element;
|
||||
this.element.replaceChild(this.preview, oldHost);
|
||||
previewHost.setCancellationHandler(() => this.show(null, false, null));
|
||||
}
|
||||
} else { // Hide the key preview
|
||||
this.element.style.display = 'none';
|
||||
this.element.className = `${BASE_CLASS}`;
|
||||
|
||||
this.previewHost = null;
|
||||
const oldPreview = this.preview;
|
||||
this.preview = document.createElement('div');
|
||||
this.element.replaceChild(this.preview, oldPreview);
|
||||
}
|
||||
|
||||
// Save the key preview state
|
||||
this.key = key;
|
||||
this.state = on;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ interface EventMap {
|
|||
export class GesturePreviewHost extends EventEmitter<EventMap> {
|
||||
private readonly div: HTMLDivElement;
|
||||
private readonly label: HTMLSpanElement;
|
||||
private readonly hintLabel: HTMLSpanElement;
|
||||
private readonly previewImgContainer: HTMLDivElement;
|
||||
|
||||
private flickPreviews = new Map<string, HTMLDivElement>;
|
||||
|
|
@ -98,6 +99,24 @@ export class GesturePreviewHost extends EventEmitter<EventMap> {
|
|||
previewImgContainer.appendChild(flickPreview);
|
||||
});
|
||||
}
|
||||
|
||||
const hintLabel = this.hintLabel = document.createElement('div');
|
||||
hintLabel.className='kmw-key-popup-icon';
|
||||
|
||||
if(!isPhone) {
|
||||
hintLabel.textContent = keySpec == keySpec.hintSrc ? keySpec.hint : keySpec.hintSrc?.text;
|
||||
hintLabel.style.fontWeight= hintLabel.textContent == '\u2022' ? 'bold' : '';
|
||||
}
|
||||
|
||||
// Default positioning puts it far too close to the flick-preview bit.
|
||||
let yAdjustment = 0;
|
||||
hintLabel.style.marginTop = `-${yAdjustment}px`;
|
||||
|
||||
// b/c multitap's border forces position shifting
|
||||
let xAdjustment = 0;
|
||||
hintLabel.style.marginRight = `-${xAdjustment}px`;
|
||||
|
||||
base.appendChild(hintLabel);
|
||||
}
|
||||
|
||||
public refreshLayout() {
|
||||
|
|
@ -119,6 +138,8 @@ export class GesturePreviewHost extends EventEmitter<EventMap> {
|
|||
}
|
||||
|
||||
public scrollFlickPreview(x: number, y: number) {
|
||||
this.clearHint();
|
||||
|
||||
const scrollStyle = this.previewImgContainer.style;
|
||||
const edge = this.flickEdgeLength * FLICK_OVERFLOW_OFFSET;
|
||||
|
||||
|
|
@ -140,6 +161,10 @@ export class GesturePreviewHost extends EventEmitter<EventMap> {
|
|||
this.previewImgContainer.classList.add('flick-clear');
|
||||
}
|
||||
|
||||
public clearHint() {
|
||||
this.hintLabel.classList.add('hint-clear');
|
||||
}
|
||||
|
||||
public clearAll() {
|
||||
this.clearFlick();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ import OSKLayer from './keyboard-layout/oskLayer.js';
|
|||
import OSKLayerGroup from './keyboard-layout/oskLayerGroup.js';
|
||||
import { LengthStyle, ParsedLengthStyle } from './lengthStyle.js';
|
||||
import { defaultFontSize, getFontSizeStyle } from './fontSizeUtils.js';
|
||||
import InternalKeyTip from './input/gestures/browser/keytip.js';
|
||||
import PhoneKeyTip from './input/gestures/browser/keytip.js';
|
||||
import { TabletKeyTip } from './input/gestures/browser/tabletPreview.js';
|
||||
import CommonConfiguration from './config/commonConfiguration.js';
|
||||
|
||||
import { DEFAULT_GESTURE_PARAMS, GestureParams, gestureSetForLayout } from './input/gestures/specsForLayout.js';
|
||||
|
|
@ -1533,7 +1534,7 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
|
|||
const keyCS = getComputedStyle(key);
|
||||
const parsedHeight = Number.parseInt(keyCS.height, 10);
|
||||
const parsedWidth = Number.parseInt(keyCS.width, 10);
|
||||
const previewHost = new GesturePreviewHost(key, !!tip, parsedWidth, parsedHeight);
|
||||
const previewHost = new GesturePreviewHost(key, this.device.formFactor == 'phone', parsedWidth, parsedHeight);
|
||||
|
||||
if (tip == null) {
|
||||
const baseKey = key.key as OSKBaseKey;
|
||||
|
|
@ -1551,11 +1552,13 @@ export default class VisualKeyboard extends EventEmitter<EventMap> implements Ke
|
|||
* Create a key preview element for phone devices
|
||||
*/
|
||||
createKeyTip() {
|
||||
if(this.device.formFactor == 'phone') {
|
||||
if (this.keytip == null) {
|
||||
if (this.keytip == null) {
|
||||
if(this.device.formFactor == 'phone') {
|
||||
// For now, should only be true (in production) when keyman.isEmbedded == true.
|
||||
let constrainPopup = this.isEmbedded;
|
||||
this.keytip = new InternalKeyTip(this, constrainPopup);
|
||||
this.keytip = new PhoneKeyTip(this, constrainPopup);
|
||||
} else {
|
||||
this.keytip = new TabletKeyTip(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@
|
|||
}
|
||||
|
||||
.tablet.ios .kmw-key-layer-group {background-color: #cfd3d9}
|
||||
.tablet.ios .kmw-key.kmw-keypreview.kmw-preview-overlay { box-shadow: 0 0 5px 3px #cfd3d9 }
|
||||
.tablet.ios .kmw-key {border: none; border-bottom: solid 1px #8a8d90; box-shadow:none; border-radius: 5px;}
|
||||
.tablet.ios .kmw-key.kmw-key-default {color:#000;background-color:#fdfdfe;}
|
||||
.tablet.ios .kmw-key.kmw-key-shift,
|
||||
|
|
@ -214,6 +215,7 @@
|
|||
/* Probably best to make this its own CSS that can be optionally included? */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.tablet.ios .kmw-key-layer-group {background-color: #0f1319}
|
||||
.tablet.ios .kmw-key.kmw-keypreview.kmw-preview-overlay { box-shadow: 0 0 5px 3px #0f1319 }
|
||||
.tablet.ios .kmw-key.kmw-key-default {color:#fff;background-color:#3d3d3e}
|
||||
.tablet.ios .kmw-key.kmw-key-shift,
|
||||
.tablet.ios .kmw-key.kmw-key-special {color:#fff;background-color:#595c62;}
|
||||
|
|
@ -226,6 +228,7 @@
|
|||
.tablet .kmw-key-row {-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);}
|
||||
|
||||
.tablet.android .kmw-key-layer-group {background-color: #333;}
|
||||
.tablet.android .kmw-key.kmw-keypreview.kmw-preview-overlay { box-shadow: 0 0 5px 3px #333 }
|
||||
.tablet.android .kmw-key {border: none; border-bottom: solid 1px #8a8d90; box-shadow:none; border-radius: 5px;}
|
||||
.tablet.android .kmw-key.kmw-key-default {color:#fff;background-color:#777;}
|
||||
.tablet.android .kmw-key.kmw-key-shift,
|
||||
|
|
@ -346,6 +349,11 @@ body div.kmw-key-shift-on span.kmw-key-text {font-family:SpecialOSK !important;f
|
|||
z-index: 10002;
|
||||
}
|
||||
|
||||
.kmw-keypreview {
|
||||
z-index: 10002;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#kmw-gesture-preview {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue