Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 4x 4x 4x 4x 36x 441x 4x 4x 550x 4x 17x 17x 17x 17x 17x 44x 44x 44x 98x 125x 44x 554x 107x 44x 44x 117x 120x 99x 4x 6x 5x 91x 29x 44x 440x 352x 352x 906x 352x 352x 44x 695x 695x 377x 367x 44x 70x 70x 55x 70x 385x 381x 381x 125x | import { Keyboard } from "./keyboard.js";
import { showNotice } from "./reporting.js";
import { keyCodes } from "../keymap.js";
import { Shortcuts } from "./shortcuts.js";
import { RunMode } from "./emulation-loop.js";
const PasteBoxId = "paste-text";
/** The eight accessibility switches, on the number keys. */
const SwitchKeys = ["K1", "K2", "K3", "K4", "K5", "K6", "K7", "K8"];
const NotTypedIntoInputs = ["checkbox", "radio", "range", "file", "color", "button", "submit", "reset", "image"];
const TypingTargets = [
"textarea",
"select",
'[contenteditable]:not([contenteditable="false"])',
`input:not(${NotTypedIntoInputs.map((type) => `[type="${type}"]`).join(", ")})`,
].join(", ");
const typingIntoField = () => !!document.activeElement?.matches(TypingTargets);
// Where keys are for the page, not the machine: the paste box, the media window's controls and panels' text fields.
const KeyboardSinks = `#${PasteBoxId}, #media-panel, .panel-field`;
// The media window's own shortcuts have to answer from inside it, or it cannot be re-aimed from
// the keyboard at all. The paste box keeps every key, because on a Mac Alt is Option and types
// an accented character rather than nothing.
const ShortcutSinks = `#${PasteBoxId}`;
const inKeyboardSink = () => !!document.activeElement?.closest(KeyboardSinks);
/** A pause's own keys, beneath any panel's; see panel-keys.js for the shape. */
export const PauseKeys = [
{ combo: "g", key: "g", run: "go", description: "Resume" },
{ combo: "n", key: "n", run: "step", description: "Run one frame" },
];
// Chrome resets the input method when focus moves into a text field and out again.
function resetInputMethod() {
const focused = document.activeElement;
const textField = document.getElementById("ime-reset");
textField.focus({ preventScroll: true });
textField.blur();
focused?.focus({ preventScroll: true, focusVisible: false });
}
/**
* Builds the emulated keyboard and wires the browser's shortcuts around it,
* exposing it as `keyboard` for whoever needs the machine's keys.
*/
export class KeyboardSetup {
/**
* @param {object} opts
* @param {object} opts.actions what each shortcut does, supplied late-bound:
* toggleFast, toggleRewind, openPrinter, openMedia, paste, onAnyKeyDown
* @param {import("./accessibility-switches.js").AccessibilitySwitches} opts.accessibilitySwitches
* @param {import("./emulation-loop.js").EmulationLoop} opts.loop
* @param {import("./debug.js").Debugger} opts.dbgr
* @param {import("./panel-keys.js").PanelKeys} opts.keys the panels' keys, answered while the machine is stopped
*/
constructor({ actions, accessibilitySwitches, processor, loop, dbgr, keys, keyLayout }) {
this.loop = loop;
this.keys = keys;
keys.register({
table: PauseKeys,
actions: { go: () => loop.go(), step: () => loop.step() },
isLive: () => loop.mode === RunMode.paused,
panel: null,
});
const keyboard = (this.keyboard = new Keyboard({
processor,
inputEnabledFunction: inKeyboardSink,
shortcutsBlockedFunction: () => !!document.activeElement?.closest(ShortcutSinks),
runningFunction: () => loop.isRunning(),
resetInputMethodFunction: resetInputMethod,
keyLayout,
}));
keyboard.addEventListener("notice", showNotice);
const runners = {
toggleDebugger: () => dbgr.toggleDebugger(),
toggleDebugPanels: () => dbgr.togglePanels(),
togglePause: () => (loop.isRunning() ? loop.stop(false) : loop.go()),
toggleFast: () => actions.toggleFast(),
openPrinter: () => actions.openPrinter(),
toggleRewind: () => actions.toggleRewind(),
openMediaDrive: (shift) => actions.openMedia(shift ? 1 : 0),
openMediaTape: () => actions.openMedia("tape"),
};
for (const shortcut of Shortcuts) {
if (!shortcut.key) continue;
const run = runners[shortcut.run];
keyboard.registerAltKeyHandler(keyCodes[shortcut.key], (down, _code, shift) => {
if (down) run(shift);
});
}
// Alt means the underlying key is never forwarded to the BBC Micro (keyboard.js bails
// out early when a handler fires), so typing numbers works normally.
const handleSwitch = (index) => (down) => accessibilitySwitches.setSwitch(index, down);
SwitchKeys.forEach((name, index) => keyboard.registerAltKeyHandler(keyCodes[name], handleSwitch(index)));
document.addEventListener("keydown", (evt) => {
actions.onAnyKeyDown();
keyboard.keyDown(evt);
});
document.addEventListener("keypress", (evt) => this.keyPress(evt));
document.addEventListener("keyup", (evt) => keyboard.keyUp(evt));
document.addEventListener("paste", (evt) => {
const target = document.activeElement;
if (target?.id !== PasteBoxId && typingIntoField()) return;
const text = evt.clipboardData?.getData("text/plain");
if (text) actions.paste(text);
});
}
/**
* While the machine is stopped, the panels' keys, and a pause's, are the characters they print, not positions.
* While it runs every key is the machine's.
*/
keyPress(evt) {
if (inKeyboardSink() || typingIntoField()) return;
const { mode } = this.loop;
if (mode !== RunMode.paused && mode !== RunMode.debugging) return;
if (this.keys.press(evt.key)) evt.preventDefault();
}
}
|