Initial working Pravetz 82 keyboard (#3)

This commit is contained in:
Kaloyan Tenchov 2023-11-25 15:52:54 -05:00 committed by GitHub
parent 057ab68add
commit 9baa7cb843
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 309 additions and 21 deletions

View File

@ -393,6 +393,10 @@ a.button:hover {
width: 570px; width: 570px;
} }
#keyboard.layout-pravetz82 .row {
width: 598px;
}
.apple2e #keyboard .row { .apple2e #keyboard .row {
width: 610px; width: 610px;
} }
@ -401,6 +405,10 @@ a.button:hover {
margin-left: 20px; margin-left: 20px;
} }
#keyboard.layout-pravetz82 .row0 {
margin-left: 10px;
}
#keyboard .row2 { #keyboard .row2 {
margin-left: 10px; margin-left: 10px;
} }
@ -409,6 +417,10 @@ a.button:hover {
margin-left: 10px; margin-left: 10px;
} }
#keyboard.layout-pravetz82 .row3 {
margin-left: 25px;
}
#keyboard .row4 { #keyboard .row4 {
margin-left: 10px; margin-left: 10px;
} }
@ -514,11 +526,79 @@ a.button:hover {
bottom: 15px; bottom: 15px;
} }
#keyboard.layout-pravetz82 .key-RST {
border-left-color: #d00;
border-top-color: #d00;
border-right-color: #700;
border-bottom-color: #700;
background-color: #a00;
}
#keyboard.layout-pravetz82 .key-RST:active {
border-left-color: #800;
border-top-color: #800;
border-right-color: #000;
border-bottom-color: #000;
background-color: #500;
}
#keyboard.layout-pravetz82 .key-ОСВ, #keyboard .key-МК {
border-left-color: #aaa;
border-top-color: #aaa;
border-right-color: #444;
border-bottom-color: #444;
background-color: #777;
}
#keyboard.layout-pravetz82 .key-ОСВ:active, #keyboard .key-МК:active {
border-left-color: #777;
border-top-color: #777;
border-right-color: #000;
border-bottom-color: #000;
background-color: #333;
}
#keyboard.layout-pravetz82 .key-ЛАТ {
border-left-color: #ddd;
border-top-color: #ddd;
border-right-color: #888;
border-bottom-color: #888;
background-color: #bbb;
}
#keyboard.layout-pravetz82 .key-ЛАТ:active {
border-left-color: #888;
border-top-color: #888;
border-right-color: #222;
border-bottom-color: #222;
background-color: #999;
}
#keyboard.layout-pravetz82 .key-ЛАТ2 {
border-left-color: #dd0;
border-top-color: #dd0;
border-right-color: #880;
border-bottom-color: #880;
background-color: #aa0;
}
#keyboard.layout-pravetz82 .key-ЛАТ2:active {
border-left-color: #770;
border-top-color: #770;
border-right-color: #000;
border-bottom-color: #000;
background-color: #550;
}
#keyboard .key-nbsp { #keyboard .key-nbsp {
margin-left: 62px; margin-left: 62px;
width: 330px; width: 330px;
} }
#keyboard.layout-pravetz82 .key-nbsp {
margin-left: 72px;
}
.apple2e #display { .apple2e #display {
margin: 5px; margin: 5px;
} }

View File

@ -35,6 +35,7 @@ export interface Apple2Props {
gl: boolean; gl: boolean;
rom: string; rom: string;
sectors: SupportedSectors; sectors: SupportedSectors;
keyboardLayout: string;
} }
/** /**
@ -47,7 +48,7 @@ export interface Apple2Props {
* @returns * @returns
*/ */
export const Apple2 = (props: Apple2Props) => { export const Apple2 = (props: Apple2Props) => {
const { e, enhanced, sectors } = props; const { e, enhanced, sectors, keyboardLayout } = props;
const screenRef = useRef<HTMLCanvasElement>(null); const screenRef = useRef<HTMLCanvasElement>(null);
const [apple2, setApple2] = useState<Apple2Impl>(); const [apple2, setApple2] = useState<Apple2Impl>();
const [error, setError] = useState<unknown>(); const [error, setError] = useState<unknown>();
@ -161,7 +162,7 @@ export const Apple2 = (props: Apple2Props) => {
</Inset> </Inset>
<ControlStrip apple2={apple2} e={e} toggleDebugger={toggleDebugger} /> <ControlStrip apple2={apple2} e={e} toggleDebugger={toggleDebugger} />
<Inset> <Inset>
<Keyboard apple2={apple2} e={e} /> <Keyboard apple2={apple2} layout={keyboardLayout} />
</Inset> </Inset>
<ErrorModal error={error} setError={setError} /> <ErrorModal error={error} setError={setError} />
</div> </div>

View File

@ -96,7 +96,7 @@ export const Key = ({
*/ */
export interface KeyboardProps { export interface KeyboardProps {
apple2: Apple2Impl | undefined; apple2: Apple2Impl | undefined;
e: boolean; layout: string;
} }
/** /**
@ -107,10 +107,10 @@ export interface KeyboardProps {
* @param apple2 Apple2 object * @param apple2 Apple2 object
* @returns Keyboard component * @returns Keyboard component
*/ */
export const Keyboard = ({ apple2, e }: KeyboardProps) => { export const Keyboard = ({ apple2, layout }: KeyboardProps) => {
const [pressed, setPressed] = useState<string[]>([]); const [pressed, setPressed] = useState<string[]>([]);
const [active, setActive] = useState<string[]>(['LOCK']); const [active, setActive] = useState<string[]>(['LOCK']);
const keys = useMemo(() => keysAsTuples(e ? keys2e : keys2), [e]); const keys = useMemo(() => keysAsTuples(layout==='apple2e' ? keys2e : keys2), [layout]);
// Set global keystroke handler // Set global keystroke handler
useEffect(() => { useEffect(() => {

View File

@ -4,6 +4,7 @@ export interface SystemType {
e: boolean; e: boolean;
enhanced: boolean; enhanced: boolean;
sectors: 13 | 16; sectors: 13 | 16;
keyboardLayout: string;
} }
// Enhanced Apple //e // Enhanced Apple //e
@ -14,6 +15,7 @@ export const defaultSystem = {
e: true, e: true,
enhanced: true, enhanced: true,
sectors: 16, sectors: 16,
keyboardLayout: 'apple2e',
} as const; } as const;
export const systemTypes: Record<string, Partial<SystemType>> = { export const systemTypes: Record<string, Partial<SystemType>> = {
@ -36,41 +38,49 @@ export const systemTypes: Record<string, Partial<SystemType>> = {
rom: 'intbasic', rom: 'intbasic',
characterRom: 'apple2_char', characterRom: 'apple2_char',
e: false, e: false,
keyboardLayout: 'apple2',
}, },
apple213: { apple213: {
rom: 'intbasic', rom: 'intbasic',
characterRom: 'apple2_char', characterRom: 'apple2_char',
e: false, e: false,
sectors: 13, sectors: 13,
keyboardLayout: 'apple2',
}, },
original: { original: {
rom: 'original', rom: 'original',
characterRom: 'apple2_char', characterRom: 'apple2_char',
e: false, e: false,
keyboardLayout: 'apple2',
}, },
apple2jplus: { apple2jplus: {
rom: 'apple2j', rom: 'apple2j',
characterRom: 'apple2j_char', characterRom: 'apple2j_char',
e: false, e: false,
keyboardLayout: 'apple2',
}, },
apple2pig: { apple2pig: {
rom: 'fpbasic', rom: 'fpbasic',
characterRom: 'pigfont_char', characterRom: 'pigfont_char',
e: false, e: false,
keyboardLayout: 'apple2',
}, },
apple2lc:{ apple2lc:{
rom: 'fpbasic', rom: 'fpbasic',
characterRom: 'apple2lc_char', characterRom: 'apple2lc_char',
e: false, e: false,
keyboardLayout: 'apple2',
}, },
apple2plus: { apple2plus: {
rom: 'fpbasic', rom: 'fpbasic',
characterRom: 'apple2_char', characterRom: 'apple2_char',
e: false, e: false,
keyboardLayout: 'apple2',
}, },
pravetz82: { pravetz82: {
rom: 'pravetz82', rom: 'pravetz82',
characterRom: 'pravetz82_char', characterRom: 'pravetz82_char',
e: false, e: false,
keyboardLayout: 'apple2',
} }
} as const; } as const;

View File

@ -19,40 +19,49 @@ const romVersion = prefs.readPref('computer_type2');
let rom: string; let rom: string;
let characterRom: string; let characterRom: string;
let sectors: SupportedSectors = 16; let sectors: SupportedSectors = 16;
let keyboardLayout: string;
switch (romVersion) { switch (romVersion) {
case 'apple2': case 'apple2':
rom = 'intbasic'; rom = 'intbasic';
characterRom = 'apple2_char'; characterRom = 'apple2_char';
keyboardLayout = 'apple2';
break; break;
case 'apple213': case 'apple213':
rom = 'intbasic'; rom = 'intbasic';
characterRom = 'apple2_char'; characterRom = 'apple2_char';
sectors = 13; sectors = 13;
keyboardLayout = 'apple2';
break; break;
case 'original': case 'original':
rom = 'original'; rom = 'original';
characterRom = 'apple2_char'; characterRom = 'apple2_char';
keyboardLayout = 'apple2';
break; break;
case 'apple2jplus': case 'apple2jplus':
rom = 'apple2j'; rom = 'apple2j';
characterRom = 'apple2j_char'; characterRom = 'apple2j_char';
keyboardLayout = 'apple2';
break; break;
case 'apple2pig': case 'apple2pig':
rom = 'fpbasic'; rom = 'fpbasic';
characterRom = 'pigfont_char'; characterRom = 'pigfont_char';
keyboardLayout = 'apple2';
break; break;
case 'apple2lc': case 'apple2lc':
rom = 'fpbasic'; rom = 'fpbasic';
characterRom = 'apple2lc_char'; characterRom = 'apple2lc_char';
keyboardLayout = 'apple2';
break; break;
case 'pravetz82': case 'pravetz82':
rom = 'pravetz82'; rom = 'pravetz82';
characterRom = 'pravetz82_char'; characterRom = 'pravetz82_char';
keyboardLayout = 'pravetz82';
break; break;
default: default:
rom = 'fpbasic'; rom = 'fpbasic';
characterRom = 'apple2_char'; characterRom = 'apple2_char';
keyboardLayout = 'apple2';
} }
const options = { const options = {
@ -91,5 +100,5 @@ apple2.ready.then(() => {
cpu.addPageHandler(lc); cpu.addPageHandler(lc);
initUI(apple2, disk2, smartport, printer, false); initUI(apple2, disk2, smartport, printer, false, keyboardLayout);
}).catch(console.error); }).catch(console.error);

View File

@ -18,26 +18,31 @@ const romVersion = prefs.readPref('computer_type2e');
let enhanced = false; let enhanced = false;
let rom: string; let rom: string;
let characterRom: string; let characterRom: string;
let keyboardLayout: string;
switch (romVersion) { switch (romVersion) {
case 'apple2e': case 'apple2e':
rom = 'apple2e'; rom = 'apple2e';
characterRom = 'apple2e_char'; characterRom = 'apple2e_char';
keyboardLayout = 'apple2e';
break; break;
case 'apple2rm': case 'apple2rm':
rom = 'apple2e'; rom = 'apple2e';
characterRom = 'rmfont_char'; characterRom = 'rmfont_char';
enhanced = true; enhanced = true;
keyboardLayout = 'apple2e';
break; break;
case 'apple2ex': case 'apple2ex':
rom = 'apple2ex'; rom = 'apple2ex';
characterRom = 'apple2enh_char'; characterRom = 'apple2enh_char';
enhanced = true; enhanced = true;
keyboardLayout = 'apple2e';
break; break;
default: default:
rom = 'apple2enh'; rom = 'apple2enh';
characterRom = 'apple2enh_char'; characterRom = 'apple2enh_char';
enhanced = true; enhanced = true;
keyboardLayout = 'apple2e';
} }
const options = { const options = {
@ -73,5 +78,5 @@ apple2.ready.then(() => {
io.setSlot(6, disk2); io.setSlot(6, disk2);
io.setSlot(7, smartport); io.setSlot(7, smartport);
initUI(apple2, disk2, smartport, printer, options.e); initUI(apple2, disk2, smartport, printer, options.e, keyboardLayout);
}).catch(console.error); }).catch(console.error);

View File

@ -851,7 +851,9 @@ function onLoaded(
apple2: Apple2, apple2: Apple2,
disk2: DiskII, disk2: DiskII,
massStorage: MassStorage<BlockFormat>, massStorage: MassStorage<BlockFormat>,
printer: Printer, e: boolean printer: Printer,
e: boolean,
keyboardLayout: string
) { ) {
_apple2 = apple2; _apple2 = apple2;
cpu = _apple2.getCPU(); cpu = _apple2.getCPU();
@ -881,7 +883,7 @@ function onLoaded(
MicroModal.init(); MicroModal.init();
keyboard = new KeyBoard(cpu, io, e); keyboard = new KeyBoard(cpu, io, keyboardLayout);
keyboard.create('#keyboard'); keyboard.create('#keyboard');
keyboard.setFunction('F1', () => cpu.reset()); keyboard.setFunction('F1', () => cpu.reset());
keyboard.setFunction('F2', (event) => { keyboard.setFunction('F2', (event) => {
@ -988,8 +990,10 @@ export function initUI(
apple2: Apple2, apple2: Apple2,
disk2: DiskII, disk2: DiskII,
massStorage: MassStorage<BlockFormat>, massStorage: MassStorage<BlockFormat>,
printer: Printer, e: boolean) { printer: Printer,
e: boolean,
keyboardLayout: string) {
window.addEventListener('load', () => { window.addEventListener('load', () => {
onLoaded(apple2, disk2, massStorage, printer, e); onLoaded(apple2, disk2, massStorage, printer, e, keyboardLayout);
}); });
} }

View File

@ -200,7 +200,25 @@ const keys2e = [
type Key2e = DeepMemberOf<typeof keys2e>; type Key2e = DeepMemberOf<typeof keys2e>;
type Key = Key2 | Key2e; const keyspravetz82 = [
[
['!', '"', '#', '¤', '%', '&', '\'', '(', ')', '0', '*', '=', '﹁', 'RST'],
['ОСВ', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', 'RPT', 'RETURN'],
['МК', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '+', '[', ']', '&darr;'],
['ЛАТ', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 'ЛАТ', АТ2'],
['ВКЛ', '&nbsp;']
], [
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', ':', '-', 'Ч', 'RST'],
['ОСВ', 'Я', 'В', 'Е', 'Р', 'Т', 'Ъ', 'У', 'И', 'О', 'П', 'Ю', 'RPT', 'RETURN'],
['МК', 'А', 'С', 'Д', 'Ф', 'Г', 'Х', 'Й', 'К', 'Л', ';', 'Ш', 'Щ', '&darr;'],
['ЛАТ', 'З', 'Ь', 'Ц', 'Ж', 'Б', 'Н', 'М', ',', '.', '/', 'ЛАТ', АТ2'],
['ВКЛ', '&nbsp;']
]
] as const;
type KeyPravetz82 = DeepMemberOf<typeof keyspravetz82>;
type Key = Key2 | Key2e | KeyPravetz82;
type KeyFunction = (key: KeyboardEvent) => void; type KeyFunction = (key: KeyboardEvent) => void;
@ -220,8 +238,20 @@ export default class KeyBoard {
private functions: Record<string, KeyFunction> = {}; private functions: Record<string, KeyFunction> = {};
constructor(private cpu: CPU6502, private io: Apple2IO, private e: boolean) { constructor(private cpu: CPU6502, private io: Apple2IO, private layout: string) {
this.keys = e ? keys2e : keys2; switch (this.layout) {
case 'apple2e':
this.keys = keys2e;
break;
case 'pravetz82':
this.keys = keyspravetz82;
this.capslocked = false; // Pravetz 82 starts with CAPS LOCK off.
break;
default:
this.keys = keys2;
break;
}
window.addEventListener('keydown', this.keydown); window.addEventListener('keydown', this.keydown);
window.addEventListener('keyup', this.keyup); window.addEventListener('keyup', this.keyup);
@ -260,7 +290,7 @@ export default class KeyBoard {
} }
shiftKey(down: boolean) { shiftKey(down: boolean) {
const shiftKeys = this.kb.querySelectorAll('.key-SHIFT'); const shiftKeys = this.kb.querySelectorAll(this.layout !== 'pravetz82' ? '.key-SHIFT' : '.key-ЛАТ');
this.shifted = down; this.shifted = down;
if (down) { if (down) {
this.io.buttonUp(2); this.io.buttonUp(2);
@ -272,7 +302,7 @@ export default class KeyBoard {
} }
controlKey(down: boolean) { controlKey(down: boolean) {
const ctrlKey = this.kb.querySelector('.key-CTRL'); const ctrlKey = this.kb.querySelector(this.layout !== 'pravetz82' ? '.key-CTRL' : '.key-МК');
this.controlled = down; this.controlled = down;
if (down) { if (down) {
ctrlKey!.classList.add('active'); ctrlKey!.classList.add('active');
@ -319,7 +349,7 @@ export default class KeyBoard {
* otherwise the used state is set to true. * otherwise the used state is set to true.
*/ */
capslockKey(down?: boolean | undefined) { capslockKey(down?: boolean | undefined) {
const capsLock = this.kb.querySelector('.key-LOCK'); const capsLock = this.kb.querySelector(this.layout !== 'pravetz82' ? '.key-LOCK' : '.key-ЛАТ2');
if (arguments.length === 0) { if (arguments.length === 0) {
if (this.capslockKeyUsed) { if (this.capslockKeyUsed) {
@ -349,6 +379,7 @@ export default class KeyBoard {
create(el: string) { create(el: string) {
this.kb = document.querySelector(el)!; this.kb = document.querySelector(el)!;
this.kb.classList.add('layout-' + this.layout);
let x, y, row, key, label, label1, label2; let x, y, row, key, label, label1, label2;
const buildLabel = (k: string) => { const buildLabel = (k: string) => {
@ -419,7 +450,15 @@ export default class KeyBoard {
ev.preventDefault(); ev.preventDefault();
target.classList.add('pressed'); target.classList.add('pressed');
let key: string = this.shifted ? key2 : key1; let key: string;
if (this.layout !== 'pravetz82') {
key = this.shifted ? key2 : key1;
}
else {
// In Pravetz 82, the operation of the shift key is inverted.
// The top row (cyrillic) is used by default and shift switches to using the bottow row (latin).
key = this.shifted ? key1 : key2;
}
switch (key) { switch (key) {
case 'BELL': case 'BELL':
key = 'G'; key = 'G';
@ -440,7 +479,12 @@ export default class KeyBoard {
key = '\x15'; key = '\x15';
break; break;
case '&darr;': case '&darr;':
key = '\x0A'; if (this.layout !== 'pravetz82') {
key = '\x0A';
}
else {
// On Pravetz 82 this key has no action.
}
break; break;
case '&uarr;': case '&uarr;':
key = '\x0B'; key = '\x0B';
@ -455,24 +499,152 @@ export default class KeyBoard {
break; break;
} }
if (this.layout === 'pravetz82') {
// Pravetz 82 specific remapping.
// Lower-case lattin letters are replaced with cyrillic capital letters.
switch (key) {
// First row.
case 'Ч':
key = '^';
break;
case '﹁':
// FIXME: Which character should this map to?
break;
// Second row.
case 'ОСВ': // Pravetz 82 ESC key in cyrillic.
key = '\x1B';
break;
case 'Я':
key = 'q';
break;
case 'В':
key = 'w';
break;
case 'Е':
key = 'e';
break;
case 'Р':
key = 'r';
break;
case 'Т':
key = 't';
break;
case 'Ъ':
key = 'y';
break;
case 'У':
key = 'u';
break;
case 'И':
key = 'i';
break;
case 'О':
key = 'o';
break;
case 'П':
key = 'p';
break;
case 'Ю':
key = '@';
break;
case '@':
key = '`';
break;
// Third row.
case 'А':
key = 'a';
break;
case 'С':
key = 's';
break;
case 'Д':
key = 'd';
break;
case 'Ф':
key = 'f';
break;
case 'Г':
key = 'g';
break;
case 'Х':
key = 'h';
break;
case 'Й':
key = 'j';
break;
case 'К':
key = 'k';
break;
case 'Л':
key = 'l';
break;
case 'Ш':
key = '[';
break;
case 'Щ':
key = ']';
break;
case '[':
key = '{';
break;
case ']':
key = '}';
break;
// Fourth row.
case 'З':
key = 'z';
break;
case 'Ь':
key = 'x';
break;
case 'Ц':
key = 'c';
break;
case 'Ж':
key = 'v';
break;
case 'Б':
key = 'b';
break;
case 'Н':
key = 'n';
break;
case 'М':
key = 'm';
break;
default:
break;
}
}
if (key.length > 1) { if (key.length > 1) {
switch (key) { switch (key) {
case 'SHIFT': case 'SHIFT':
case 'ЛАТ': // Shift on Pravetz 82 switches to cyrillic.
this.shiftKey(!this.shifted); this.shiftKey(!this.shifted);
break; break;
case 'CTRL': case 'CTRL':
case 'МК': // Pravetz 82 CTRL key in cyrillic.
this.controlKey(!this.controlled); this.controlKey(!this.controlled);
break; break;
case 'CAPS': case 'CAPS':
case 'LOCK': case 'LOCK':
case АТ2': // CAPS LOCK on Pravetz 82 switches between cyrillic and latin.
this.capslockKey(undefined); this.capslockKey(undefined);
break; break;
case 'POW': case 'POW':
case 'POWER': case 'POWER':
case 'ВКЛ': // Pravetz 82 power key in cyrillic.
if (window.confirm('Power Cycle?')) if (window.confirm('Power Cycle?'))
window.location.reload(); window.location.reload();
break; break;
case 'RESET': case 'RESET':
case 'RST':
this.cpu.reset(); this.cpu.reset();
break; break;
case 'OPEN_APPLE': case 'OPEN_APPLE':
@ -481,15 +653,22 @@ export default class KeyBoard {
case 'CLOSED_APPLE': case 'CLOSED_APPLE':
this.optionKey(!this.optioned); this.optionKey(!this.optioned);
break; break;
case 'RPT': // Pravetz 82 "repeat" key.
// Do nothing.
break;
default: default:
break; break;
} }
} else { } else {
if (this.controlled && key >= '@' && key <= '_') { if (this.controlled && key >= '@' && key <= '_') {
this.io.keyDown(key.charCodeAt(0) - 0x40); this.io.keyDown(key.charCodeAt(0) - 0x40);
} else if (this.e && !this.shifted && !this.capslocked && } else if (this.layout==='apple2e' && !this.shifted && !this.capslocked &&
key >= 'A' && key <= 'Z') { key >= 'A' && key <= 'Z') {
this.io.keyDown(key.charCodeAt(0) + 0x20); this.io.keyDown(key.charCodeAt(0) + 0x20);
} else if (this.layout==='pravetz82' && !this.shifted && this.capslocked &&
key >= 'a' && key <= 'z') {
// CAPS LOCK on Pravetz 82 switches between cyrillic and latin.
this.io.keyDown(key.charCodeAt(0) - 0x20);
} else { } else {
this.io.keyDown(key.charCodeAt(0)); this.io.keyDown(key.charCodeAt(0));
} }
@ -506,7 +685,7 @@ export default class KeyBoard {
} }
private keydown = (evt: KeyboardEvent) => { private keydown = (evt: KeyboardEvent) => {
if (!this.dialogOpen() && (!evt.metaKey || evt.ctrlKey || this.e)) { if (!this.dialogOpen() && (!evt.metaKey || evt.ctrlKey || this.layout==='apple2e')) {
evt.preventDefault(); evt.preventDefault();
const key = this.mapKeyEvent(evt); const key = this.mapKeyEvent(evt);