mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
66f3e04d8e
The major impetus for rewriting in UI, at least. Still some ironing to do, but much nicer than my attempt to do this using the old UI "framework".
562 lines
18 KiB
TypeScript
562 lines
18 KiB
TypeScript
import { byte, DeepMemberOf, KnownKeys } from '../types';
|
|
import Apple2IO from '../apple2io';
|
|
import CPU6502 from '../cpu6502';
|
|
import { debug, toHex } from '../util';
|
|
|
|
// keycode: [plain, cntl, shift]
|
|
const keymap = {
|
|
// Most of these won't happen
|
|
0x00: [0x00, 0x00, 0x00], //
|
|
0x01: [0x01, 0x01, 0x01], //
|
|
0x02: [0x02, 0x02, 0x02], //
|
|
0x03: [0x03, 0x03, 0x03], //
|
|
0x04: [0x04, 0x04, 0x04], //
|
|
0x05: [0x05, 0x05, 0x05], //
|
|
0x06: [0x06, 0x06, 0x06], //
|
|
0x07: [0x07, 0x07, 0x07], //
|
|
0x08: [0x7F, 0x7F, 0x7F], // BS/DELETE
|
|
0x09: [0x09, 0x09, 0x09], // TAB
|
|
0x0A: [0x0A, 0x0A, 0x0A], //
|
|
0x0B: [0x0B, 0x0B, 0x0B], //
|
|
0x0C: [0x0C, 0x0C, 0x0C], //
|
|
0x0D: [0x0D, 0x0D, 0x0D], // CR
|
|
0x0E: [0x0E, 0x0E, 0x0E], //
|
|
0x0F: [0x0F, 0x0F, 0x0F], //
|
|
|
|
0x10: [0xff, 0xff, 0xff], // SHIFT
|
|
0x11: [0xff, 0xff, 0xff], // CTRL
|
|
0x12: [0xff, 0xff, 0xff], // ALT/OPTION
|
|
0x13: [0x13, 0x13, 0x13], //
|
|
0x14: [0x14, 0x14, 0x14], //
|
|
0x15: [0x15, 0x15, 0x15], //
|
|
0x16: [0x16, 0x16, 0x16], //
|
|
0x17: [0x17, 0x17, 0x18], //
|
|
0x18: [0x18, 0x18, 0x18], //
|
|
0x19: [0x19, 0x19, 0x19], //
|
|
0x1A: [0x1A, 0x1A, 0x1A], //
|
|
0x1B: [0x1B, 0x1B, 0x1B], // ESC
|
|
0x1C: [0x1C, 0x1C, 0x1C], //
|
|
0x1D: [0x1D, 0x1D, 0x1D], //
|
|
0x1E: [0x1E, 0x1E, 0x1E], //
|
|
0x1F: [0x1F, 0x1F, 0x1F], //
|
|
|
|
// Most of these besides space won't happen
|
|
0x20: [0x20, 0x20, 0x20], //
|
|
0x21: [0x21, 0x21, 0x21], //
|
|
0x22: [0x22, 0x22, 0x22], //
|
|
0x23: [0x23, 0x23, 0x23], //
|
|
0x24: [0x24, 0x24, 0x24], //
|
|
0x25: [0x08, 0x08, 0x08], // <- left
|
|
0x26: [0x0B, 0x0B, 0x0B], // ^ up
|
|
0x27: [0x15, 0x15, 0x15], // -> right
|
|
0x28: [0x0A, 0x0A, 0x0A], // v down
|
|
0x29: [0x29, 0x29, 0x29], // )
|
|
0x2A: [0x2A, 0x2A, 0x2A], // *
|
|
0x2B: [0x2B, 0x2B, 0x2B], // +
|
|
0x2C: [0x2C, 0x2C, 0x3C], // , - <
|
|
0x2D: [0x2D, 0x2D, 0x5F], // - - _
|
|
0x2E: [0x2E, 0x2E, 0x3E], // . - >
|
|
0x2F: [0x2F, 0x2F, 0x3F], // / - ?
|
|
|
|
0x30: [0x30, 0x30, 0x29], // 0 - )
|
|
0x31: [0x31, 0x31, 0x21], // 1 - !
|
|
0x32: [0x32, 0x00, 0x40], // 2 - @
|
|
0x33: [0x33, 0x33, 0x23], // 3 - #
|
|
0x34: [0x34, 0x34, 0x24], // 4 - $
|
|
0x35: [0x35, 0x35, 0x25], // 5 - %
|
|
0x36: [0x36, 0x36, 0x5E], // 6 - ^
|
|
0x37: [0x37, 0x37, 0x26], // 7 - &
|
|
0x38: [0x38, 0x38, 0x2A], // 8 - *
|
|
0x39: [0x39, 0x39, 0x28], // 9 - (
|
|
0x3A: [0x3A, 0x3A, 0x3A], // :
|
|
0x3B: [0x3B, 0x3B, 0x3A], // ; - :
|
|
0x3C: [0x3C, 0x3C, 0x3C], // <
|
|
0x3D: [0x3D, 0x3D, 0x2B], // = - +
|
|
0x3E: [0x3E, 0x3E, 0x3E], // >
|
|
0x3F: [0x3F, 0x3F, 0x3F], // ?
|
|
|
|
// Alpha and control
|
|
0x40: [0x40, 0x00, 0x40], // @
|
|
0x41: [0x61, 0x01, 0x41], // A
|
|
0x42: [0x62, 0x02, 0x42], // B
|
|
0x43: [0x63, 0x03, 0x43], // C - BRK
|
|
0x44: [0x64, 0x04, 0x44], // D
|
|
0x45: [0x65, 0x05, 0x45], // E
|
|
0x46: [0x66, 0x06, 0x46], // F
|
|
0x47: [0x67, 0x07, 0x47], // G - BELL
|
|
0x48: [0x68, 0x08, 0x48], // H
|
|
0x49: [0x69, 0x09, 0x49], // I - TAB
|
|
0x4A: [0x6A, 0x0A, 0x4A], // J - NL
|
|
0x4B: [0x6B, 0x0B, 0x4B], // K - VT
|
|
0x4C: [0x6C, 0x0C, 0x4C], // L
|
|
0x4D: [0x6D, 0x0D, 0x4D], // M - CR
|
|
0x4E: [0x6E, 0x0E, 0x4E], // N
|
|
0x4F: [0x6F, 0x0F, 0x4F], // O
|
|
|
|
0x50: [0x70, 0x10, 0x50], // P
|
|
0x51: [0x71, 0x11, 0x51], // Q
|
|
0x52: [0x72, 0x12, 0x52], // R
|
|
0x53: [0x73, 0x13, 0x53], // S
|
|
0x54: [0x74, 0x14, 0x54], // T
|
|
0x55: [0x75, 0x15, 0x55], // U
|
|
0x56: [0x76, 0x16, 0x56], // V
|
|
0x57: [0x77, 0x17, 0x57], // W
|
|
0x58: [0x78, 0x18, 0x58], // X
|
|
0x59: [0x79, 0x19, 0x59], // Y
|
|
0x5A: [0x7A, 0x1A, 0x5A], // Z
|
|
0x5B: [0xFF, 0xFF, 0xFF], // Left window
|
|
0x5C: [0xFF, 0xFF, 0xFF], // Right window
|
|
0x5D: [0xFF, 0xFF, 0xFF], // Select
|
|
0x5E: [0x5E, 0x1E, 0x5E], //
|
|
0x5F: [0x5F, 0x1F, 0x5F], // _
|
|
|
|
// Numeric pad
|
|
0x60: [0x30, 0x30, 0x30], // 0
|
|
0x61: [0x31, 0x31, 0x31], // 1
|
|
0x62: [0x32, 0x32, 0x32], // 2
|
|
0x63: [0x33, 0x33, 0x33], // 3
|
|
0x64: [0x34, 0x34, 0x34], // 4
|
|
0x65: [0x35, 0x35, 0x35], // 5
|
|
0x66: [0x36, 0x36, 0x36], // 6
|
|
0x67: [0x37, 0x37, 0x37], // 7
|
|
0x68: [0x38, 0x38, 0x38], // 8
|
|
0x69: [0x39, 0x39, 0x39], // 9
|
|
|
|
0x6A: [0x2A, 0x2A, 0x2A], // *
|
|
0x6B: [0x2B, 0x2B, 0x2B], // +
|
|
0x6D: [0x2D, 0x2D, 0x2D], // -
|
|
0x6E: [0x2E, 0x2E, 0x2E], // .
|
|
0x6F: [0x2F, 0x2F, 0x39], // /
|
|
|
|
// Stray keys
|
|
0xAD: [0x2D, 0x2D, 0x5F], // - - _
|
|
0xBA: [0x3B, 0x3B, 0x3A], // ; - :
|
|
0xBB: [0x3D, 0x3D, 0x2B], // = - +
|
|
0xBC: [0x2C, 0x2C, 0x3C], // , - <
|
|
0xBD: [0x2D, 0x2D, 0x5F], // - - _
|
|
0xBE: [0x2E, 0x2E, 0x3E], // . - >
|
|
0xBF: [0x2F, 0x2F, 0x3F], // / - ?
|
|
0xC0: [0x60, 0x60, 0x7E], // ` - ~
|
|
0xDB: [0x5B, 0x1B, 0x7B], // [ - {
|
|
0xDC: [0x5C, 0x1C, 0x7C], // \ - |
|
|
0xDD: [0x5D, 0x1D, 0x7D], // ] - }
|
|
0xDE: [0x27, 0x22, 0x22], // ' - '
|
|
|
|
0xFF: [0xFF, 0xFF, 0xFF] // No comma line
|
|
} as const;
|
|
|
|
function isKeyboardCode(code: number): code is KnownKeys<typeof keymap> {
|
|
return code in keymap;
|
|
}
|
|
|
|
const uiKitMap = {
|
|
'Dead': 0xFF,
|
|
'UIKeyInputLeftArrow': 0x08,
|
|
'UIKeyInputRightArrow': 0x15,
|
|
'UIKeyInputUpArrow': 0x0B,
|
|
'UIKeyInputDownArrow': 0x0A,
|
|
'UIKeyInputEscape': 0x1B
|
|
} as const;
|
|
|
|
|
|
function isUiKitKey(k: string): k is KnownKeys<typeof uiKitMap> {
|
|
return k in uiKitMap;
|
|
}
|
|
|
|
const keys2 = [
|
|
[
|
|
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', ':', '-', 'RESET'],
|
|
['ESC', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'REPT', 'RETURN'],
|
|
['CTRL', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', '←', '→'],
|
|
['SHIFT', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'SHIFT'],
|
|
['POWER', ' ']
|
|
], [
|
|
['!', '"', '#', '$', '%', '&', '\'', '(', ')', '0', '*', '=', 'RESET'],
|
|
['ESC', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', '@', 'REPT', 'RETURN'],
|
|
['CTRL', 'A', 'S', 'D', 'F', 'BELL', 'H', 'J', 'K', 'L', '+', '←', '→'],
|
|
['SHIFT', 'Z', 'X', 'C', 'V', 'B', '^', ']', '<', '>', '?', 'SHIFT'],
|
|
['POWER', ' ']
|
|
]
|
|
] as const;
|
|
|
|
|
|
type Key2 = DeepMemberOf<typeof keys2>;
|
|
|
|
const keys2e = [
|
|
[
|
|
['ESC', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'DELETE'],
|
|
['TAB', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', '\\'],
|
|
['CTRL', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', '"', 'RETURN'],
|
|
['SHIFT', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'SHIFT'],
|
|
['LOCK', '`', 'POW', 'OPEN_APPLE', ' ', 'CLOSED_APPLE', '←', '→', '↓', '↑']
|
|
], [
|
|
['ESC', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', 'DELETE'],
|
|
['TAB', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'],
|
|
['CTRL', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '\'', 'RETURN'],
|
|
['SHIFT', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 'SHIFT'],
|
|
['CAPS', '~', 'POW', 'OPEN_APPLE', ' ', 'CLOSED_APPLE', '←', '→', '↓', '↑']
|
|
]
|
|
] as const;
|
|
|
|
type Key2e = DeepMemberOf<typeof keys2e>;
|
|
|
|
type Key = Key2 | Key2e;
|
|
|
|
type KeyFunction = (key: KeyboardEvent) => void;
|
|
|
|
export default class KeyBoard {
|
|
private kb: HTMLElement;
|
|
private keys;
|
|
|
|
private shifted = false;
|
|
private controlled = false;
|
|
private capslocked = true;
|
|
|
|
// Initially caps lock on physical keyboard is assumed to be off,
|
|
// but on emulated keyboard it is on.
|
|
private capslockKeyUsed = false;
|
|
private optioned = false;
|
|
private commanded = false;
|
|
|
|
private functions: Record<string, KeyFunction> = {};
|
|
|
|
constructor(private cpu: CPU6502, private io: Apple2IO, private e: boolean) {
|
|
this.keys = e ? keys2e : keys2;
|
|
|
|
window.addEventListener('keydown', this.keydown);
|
|
window.addEventListener('keyup', this.keyup);
|
|
}
|
|
|
|
setFunction(key: string, fn: KeyFunction) {
|
|
this.functions[key] = fn;
|
|
}
|
|
|
|
mapKeyEvent(evt: KeyboardEvent) {
|
|
const code = evt.keyCode;
|
|
let key: byte = 0xff;
|
|
|
|
if (isUiKitKey(evt.key)) {
|
|
key = uiKitMap[evt.key];
|
|
} else if (isKeyboardCode(code)) {
|
|
key = keymap[code][evt.shiftKey ? 2 : (evt.ctrlKey ? 1 : 0)];
|
|
|
|
if (code !== 20 && this.capslockKeyUsed) {
|
|
this.capslockKey(evt.getModifierState('CapsLock'));
|
|
}
|
|
|
|
if (this.capslocked && key >= 0x61 && key <= 0x7A) {
|
|
key -= 0x20;
|
|
}
|
|
} else {
|
|
debug('Unhandled key = ' + toHex(code));
|
|
}
|
|
|
|
if (key === 0x7F && evt.shiftKey && evt.ctrlKey) {
|
|
this.cpu.reset();
|
|
key = 0xff;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
shiftKey(down: boolean) {
|
|
const shiftKeys = this.kb.querySelectorAll('.key-SHIFT');
|
|
this.shifted = down;
|
|
if (down) {
|
|
this.io.buttonUp(2);
|
|
shiftKeys.forEach((key) => { key.classList.add('active'); });
|
|
} else {
|
|
this.io.buttonDown(2);
|
|
shiftKeys.forEach((key) => { key.classList.remove('active'); });
|
|
}
|
|
}
|
|
|
|
controlKey(down: boolean) {
|
|
const ctrlKey = this.kb.querySelector('.key-CTRL');
|
|
this.controlled = down;
|
|
if (down) {
|
|
ctrlKey!.classList.add('active');
|
|
} else {
|
|
ctrlKey!.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
commandKey(down: boolean) {
|
|
const commandKey = this.kb.querySelector('.key-OPEN_APPLE');
|
|
if (!commandKey) {
|
|
return;
|
|
}
|
|
this.commanded = down;
|
|
if (down) {
|
|
this.io.buttonDown(0);
|
|
commandKey.classList.add('active');
|
|
} else {
|
|
this.io.buttonUp(0);
|
|
commandKey.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
optionKey(down: boolean) {
|
|
const optionKey = this.kb.querySelector('.key-CLOSED_APPLE');
|
|
if (!optionKey) {
|
|
return;
|
|
}
|
|
this.optioned = down;
|
|
if (down) {
|
|
this.io.buttonDown(1);
|
|
optionKey.classList.add('active');
|
|
} else {
|
|
this.io.buttonUp(1);
|
|
optionKey.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the state of the Caps Lock key. It is very complicated.
|
|
* @param down if `true`, Caps Lock is pressed; if `false` Caps Lock is not pressed;
|
|
* if `undefined`, Caps Lock is toggled and its "used" state is set to false;
|
|
* if called with no arguments, the state is toggled _if_ it has been used before,
|
|
* otherwise the used state is set to true.
|
|
*/
|
|
capslockKey(down?: boolean | undefined) {
|
|
const capsLock = this.kb.querySelector('.key-LOCK');
|
|
|
|
if (arguments.length === 0) {
|
|
if (this.capslockKeyUsed) {
|
|
this.capslocked = !this.capslocked;
|
|
} else {
|
|
this.capslockKeyUsed = true;
|
|
}
|
|
} else if (down === undefined) {
|
|
this.capslocked = !this.capslocked;
|
|
this.capslockKeyUsed = false;
|
|
} else {
|
|
this.capslocked = down;
|
|
}
|
|
|
|
if (this.capslocked) {
|
|
capsLock!.classList.add('active');
|
|
} else {
|
|
capsLock!.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
reset(event: Event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
this.cpu.reset();
|
|
}
|
|
|
|
create(el: string) {
|
|
this.kb = document.querySelector(el)!;
|
|
let x, y, row, key, label, label1, label2;
|
|
|
|
const buildLabel = (k: string) => {
|
|
const span = document.createElement('span');
|
|
span.innerHTML = k;
|
|
if (k.length > 1 && k.slice(0, 1) !== '&')
|
|
span.classList.add('small');
|
|
return span;
|
|
};
|
|
|
|
for (y = 0; y < 5; y++) {
|
|
row = document.createElement('div');
|
|
row.classList.add('row');
|
|
row.classList.add(`row${y}`);
|
|
this.kb.append(row);
|
|
for (x = 0; x < this.keys[0][y].length; x++) {
|
|
const key1 = this.keys[0][y][x];
|
|
const key2 = this.keys[1][y][x];
|
|
|
|
label = document.createElement('div');
|
|
label1 = buildLabel(key1);
|
|
label2 = buildLabel(key2);
|
|
|
|
key = document.createElement('div');
|
|
key.classList.add('key');
|
|
key.classList.add('key-' + key1.replace(/[&#;]/g, ''));
|
|
|
|
if (key1.length > 1) {
|
|
if (key1 === 'LOCK')
|
|
key.classList.add('v-center2');
|
|
else
|
|
key.classList.add('v-center');
|
|
}
|
|
if (key1 !== key2) {
|
|
key.classList.add('key-' + key2.replace(/[&;]/g, ''));
|
|
label.append(label2);
|
|
label.append(document.createElement('br'));
|
|
}
|
|
if (key1 === 'LOCK') {
|
|
key.classList.add('active');
|
|
}
|
|
|
|
label.append(label1);
|
|
key.append(label);
|
|
key.dataset.key1 = key1;
|
|
key.dataset.key2 = key2;
|
|
|
|
const mouseDown = this.genMouseDown(key, key1, key2);
|
|
const mouseUp = this.genMouseUp(key);
|
|
if (window.ontouchstart === undefined) {
|
|
|
|
key.addEventListener('mousedown', mouseDown);
|
|
key.addEventListener('mouseup', mouseUp);
|
|
key.addEventListener('mouseleave', mouseUp);
|
|
} else {
|
|
key.addEventListener('touchstart', mouseDown);
|
|
key.addEventListener('touchend', mouseUp);
|
|
key.addEventListener('touchleave', mouseUp);
|
|
}
|
|
|
|
row.append(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
private genMouseDown(target: HTMLElement, key1: Key, key2: Key) {
|
|
return (ev: MouseEvent) => {
|
|
ev.preventDefault();
|
|
target.classList.add('pressed');
|
|
|
|
let key: string = this.shifted ? key2 : key1;
|
|
switch (key) {
|
|
case 'BELL':
|
|
key = 'G';
|
|
break;
|
|
case 'RETURN':
|
|
key = '\r';
|
|
break;
|
|
case 'TAB':
|
|
key = '\t';
|
|
break;
|
|
case 'DELETE':
|
|
key = '\x7F';
|
|
break;
|
|
case '←':
|
|
key = '\x08';
|
|
break;
|
|
case '→':
|
|
key = '\x15';
|
|
break;
|
|
case '↓':
|
|
key = '\x0A';
|
|
break;
|
|
case '↑':
|
|
key = '\x0B';
|
|
break;
|
|
case ' ':
|
|
key = ' ';
|
|
break;
|
|
case 'ESC':
|
|
key = '\x1B';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (key.length > 1) {
|
|
switch (key) {
|
|
case 'SHIFT':
|
|
this.shiftKey(!this.shifted);
|
|
break;
|
|
case 'CTRL':
|
|
this.controlKey(!this.controlled);
|
|
break;
|
|
case 'CAPS':
|
|
case 'LOCK':
|
|
this.capslockKey(undefined);
|
|
break;
|
|
case 'POW':
|
|
case 'POWER':
|
|
if (window.confirm('Power Cycle?'))
|
|
window.location.reload();
|
|
break;
|
|
case 'RESET':
|
|
this.cpu.reset();
|
|
break;
|
|
case 'OPEN_APPLE':
|
|
this.commandKey(!this.commanded);
|
|
break;
|
|
case 'CLOSED_APPLE':
|
|
this.optionKey(!this.optioned);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
if (this.controlled && key >= '@' && key <= '_') {
|
|
this.io.keyDown(key.charCodeAt(0) - 0x40);
|
|
} else if (this.e && !this.shifted && !this.capslocked &&
|
|
key >= 'A' && key <= 'Z') {
|
|
this.io.keyDown(key.charCodeAt(0) + 0x20);
|
|
} else {
|
|
this.io.keyDown(key.charCodeAt(0));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private dialogOpen() {
|
|
return !!document.querySelector('.modal.is-open');
|
|
}
|
|
|
|
private genMouseUp(target: HTMLElement) {
|
|
return () => target.classList.remove('pressed');
|
|
}
|
|
|
|
private keydown = (evt: KeyboardEvent) => {
|
|
if (!this.dialogOpen() && (!evt.metaKey || evt.ctrlKey || this.e)) {
|
|
evt.preventDefault();
|
|
|
|
const key = this.mapKeyEvent(evt);
|
|
if (key !== 0xff) {
|
|
this.io.keyDown(key);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (evt.key === 'Shift') {
|
|
this.shiftKey(true);
|
|
} else if (evt.key === 'CapsLock') {
|
|
this.capslockKey();
|
|
} else if (evt.key === 'Control') {
|
|
this.controlKey(true);
|
|
} else if (evt.key === 'Meta') { // AKA Command
|
|
this.commandKey(true);
|
|
} else if (evt.key === 'Alt') {
|
|
if (evt.location === 1) {
|
|
this.commandKey(true);
|
|
} else {
|
|
this.optionKey(true);
|
|
}
|
|
} else {
|
|
if (evt.key in this.functions) {
|
|
this.functions[evt.key](evt);
|
|
evt.preventDefault();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
private keyup = (evt: KeyboardEvent) => {
|
|
if (!this.dialogOpen()) {
|
|
this.io.keyUp();
|
|
}
|
|
|
|
if (evt.key === 'Shift') { // Shift
|
|
this.shiftKey(false);
|
|
} else if (evt.key === 'Control') { // Control
|
|
this.controlKey(false);
|
|
} else if (evt.key === 'Meta') { // AKA Command
|
|
this.commandKey(false);
|
|
} else if (evt.key === 'Alt') { // Alt
|
|
if (evt.location === 1) {
|
|
this.commandKey(false);
|
|
} else {
|
|
this.optionKey(false);
|
|
}
|
|
}
|
|
};
|
|
}
|