explodorama / files / src / ui / keyOverlay.ts
keyOverlay.ts
| 1 | import { spriteUrl } from '../render/sprites' |
| 2 | import { KEY_GLYPHS, type KeyPart } from './keyDemo' |
| 3 | import { PAD_LIT_SECONDS } from './padOverlay' |
| 4 | |
| 5 | /** |
| 6 | * The keyboard demo's key overlay (Q65): a small keyboard (Kenney's Input Prompts, Keyboard & |
| 7 | * Mouse) with the keys the game uses: the arrows, Alt and Shift, M B V and Space, ? Home Enter and |
| 8 | * Esc. All white at rest, a pressed key lit for PAD_LIT_SECONDS (the controller overlay's time, so |
| 9 | * two presses never overlap). The places are a CSS grid (style.css `.key-overlay`), not art. |
| 10 | * Same shape as padOverlay.ts; shown instead of the finger. |
| 11 | */ |
| 12 | export interface KeyOverlay { |
| 13 | el: HTMLElement |
| 14 | /** Lights `part`, letting go again after `seconds` (default PAD_LIT_SECONDS). */ |
| 15 | press(part: KeyPart, seconds?: number): void |
| 16 | /** Lets go of everything at once (and drops pending releases). */ |
| 17 | releaseAll(): void |
| 18 | } |
| 19 | |
| 20 | const PARTS = Object.keys(KEY_GLYPHS) as KeyPart[] |
| 21 | |
| 22 | export function createKeyOverlay(size: 'large' | 'compact'): KeyOverlay { |
| 23 | const el = document.createElement('div') |
| 24 | el.className = `key-overlay ${size}` |
| 25 | el.setAttribute('aria-hidden', 'true') |
| 26 | const imgs = new Map<KeyPart, HTMLImageElement>() |
| 27 | for (const part of PARTS) { |
| 28 | const img = document.createElement('img') |
| 29 | img.className = `key-part ${part}` |
| 30 | img.src = spriteUrl(KEY_GLYPHS[part]) |
| 31 | img.alt = '' |
| 32 | img.draggable = false |
| 33 | imgs.set(part, img) |
| 34 | el.appendChild(img) |
| 35 | } |
| 36 | const timers = new Map<KeyPart, number>() |
| 37 | const release = (part: KeyPart): void => { |
| 38 | window.clearTimeout(timers.get(part)) |
| 39 | timers.delete(part) |
| 40 | imgs.get(part)!.classList.remove('on') |
| 41 | } |
| 42 | return { |
| 43 | el, |
| 44 | press(part, seconds = PAD_LIT_SECONDS) { |
| 45 | window.clearTimeout(timers.get(part)) |
| 46 | imgs.get(part)!.classList.add('on') |
| 47 | timers.set(part, window.setTimeout(() => release(part), seconds * 1000)) |
| 48 | }, |
| 49 | releaseAll() { |
| 50 | for (const part of PARTS) release(part) |
| 51 | }, |
| 52 | } |
| 53 | } |