Compare commits

...

5 Commits

+128 -47
View File
@@ -213,7 +213,7 @@
<input id="input" class="hidden-input" type="text" autocomplete="off" aria-hidden="true" tabindex="-1"> <input id="input" class="hidden-input" type="text" autocomplete="off" aria-hidden="true" tabindex="-1">
<div class="input-row"> <div class="input-row">
<div class="hint">Keyboard input is captured directly by the screen</div> <div class="hint">Keyboard works globally: digits, numpad, Enter, Backspace, Delete, →, +, -, *, /, %, ^, q, n, r, i, g, l, s, c, t, S, C, T</div>
<select id="angleMode"> <select id="angleMode">
<option value="deg">Degrees</option> <option value="deg">Degrees</option>
<option value="rad">Radians</option> <option value="rad">Radians</option>
@@ -286,14 +286,18 @@
}); });
} }
function getStackValue(index) {
return calc.isValidIndex(index) ? calc.stack[index] : undefined;
}
function getLineValue(line) { function getLineValue(line) {
if (calc.isEditing) { if (calc.isEditing) {
if (line === 0) { if (line === 0) {
return calc.inputValue; return calc.inputValue;
} }
return calc.stack[line - 1]; return getStackValue(line - 1);
} }
return calc.stack[line]; return getStackValue(line);
} }
function render() { function render() {
@@ -317,11 +321,7 @@
if (!calc.isEditing) return; if (!calc.isEditing) return;
if (calc.inputValue !== '') { if (calc.inputValue !== '') {
const value = calc.parseInputValue(calc.inputValue); const value = calc.parseInputValue(calc.inputValue);
if (calc.stack.length >= calc.maxSize) { calc.push(value);
throw new Error('Stack overflow');
}
calc.stack.unshift(value);
if (calc.stack.length > 4) calc.stack.length = 4;
} }
calc.inputValue = ''; calc.inputValue = '';
calc.isEditing = false; calc.isEditing = false;
@@ -330,18 +330,20 @@
function execute(name) { function execute(name) {
try { try {
if (name === 'swap') { if (name === 'enter') {
pushEditingValueIfNeeded();
if (calc.stack.length >= 2) calc.swap(0, 1);
} else if (name === 'drop') {
pushEditingValueIfNeeded();
if (calc.stack.length >= 1) calc.remove(0);
} else if (name === 'clear') {
calc.clear();
} else if (name === 'enter') {
if (calc.isEditing) { if (calc.isEditing) {
pushEditingValueIfNeeded(); pushEditingValueIfNeeded();
} else if (calc.isValidIndex(0)) {
calc.push(getStackValue(0));
} }
} else if (name === 'swap') {
pushEditingValueIfNeeded();
if (calc.isValidIndex(1)) calc.swap(0, 1);
} else if (name === 'drop') {
pushEditingValueIfNeeded();
if (calc.isValidIndex(0)) calc.remove(0);
} else if (name === 'clear') {
calc.clear();
} else { } else {
pushEditingValueIfNeeded(); pushEditingValueIfNeeded();
calc.command(name); calc.command(name);
@@ -354,7 +356,77 @@
} }
function isInputChar(key) { function isInputChar(key) {
return /^[0-9a-fA-F.+\-]$/.test(key); return /^[0-9a-fA-F.]$/.test(key);
}
function shouldIgnoreKeyboardEvent(event) {
const target = event.target;
if (!target) return false;
const tagName = target.tagName;
return (
tagName === 'INPUT' ||
tagName === 'TEXTAREA' ||
tagName === 'SELECT' ||
target.isContentEditable
);
}
function getKeyboardAction(event) {
const numpadMap = {
Numpad0: { type: 'input', value: '0' },
Numpad1: { type: 'input', value: '1' },
Numpad2: { type: 'input', value: '2' },
Numpad3: { type: 'input', value: '3' },
Numpad4: { type: 'input', value: '4' },
Numpad5: { type: 'input', value: '5' },
Numpad6: { type: 'input', value: '6' },
Numpad7: { type: 'input', value: '7' },
Numpad8: { type: 'input', value: '8' },
Numpad9: { type: 'input', value: '9' },
NumpadDecimal: { type: 'input', value: '.' },
NumpadAdd: { type: 'command', value: 'add' },
NumpadSubtract: { type: 'command', value: 'sub' },
NumpadMultiply: { type: 'command', value: 'mul' },
NumpadDivide: { type: 'command', value: 'div' },
NumpadEnter: { type: 'command', value: 'enter' },
};
if (numpadMap[event.code]) {
return numpadMap[event.code];
}
if (isInputChar(event.key)) {
return { type: 'input', value: event.key };
}
const keyMap = {
Enter: { type: 'command', value: 'enter' },
Backspace: { type: 'stackOrEdit', value: 'drop' },
Delete: { type: 'command', value: 'clear' },
Escape: { type: 'cancelEdit' },
ArrowRight: { type: 'command', value: 'swap' },
'+': { type: 'command', value: 'add' },
'-': { type: 'command', value: 'sub' },
'*': { type: 'command', value: 'mul' },
'/': { type: 'command', value: 'div' },
'%': { type: 'command', value: 'mod' },
'^': { type: 'command', value: 'pow' },
q: { type: 'command', value: 'sqr' },
n: { type: 'command', value: 'neg' },
r: { type: 'command', value: 'sqrt' },
i: { type: 'command', value: 'recip' },
g: { type: 'command', value: 'log' },
l: { type: 'command', value: 'ln' },
s: { type: 'command', value: 'sin' },
c: { type: 'command', value: 'cos' },
t: { type: 'command', value: 'tan' },
S: { type: 'command', value: 'asin' },
C: { type: 'command', value: 'acos' },
T: { type: 'command', value: 'atan' },
};
return keyMap[event.key] || null;
} }
function focusScreen() { function focusScreen() {
@@ -382,47 +454,56 @@
syncInputFromState(); syncInputFromState();
} }
screen.addEventListener('keydown', (event) => { function handleKeydown(event) {
if (shouldIgnoreKeyboardEvent(event)) {
return;
}
const action = getKeyboardAction(event);
if (!action) {
return;
}
try { try {
if (event.key === 'Enter') { if (action.type === 'cancelEdit') {
event.preventDefault(); if (!calc.isEditing) {
execute('enter'); return;
return;
}
if (event.key === 'Backspace') {
event.preventDefault();
if (calc.isEditing) {
editXWithKey('Backspace');
render();
} }
return;
}
if (isInputChar(event.key)) {
event.preventDefault(); event.preventDefault();
editXWithKey(event.key); calc.inputValue = '';
calc.isEditing = false;
syncInputFromState();
render(); render();
return; return;
} }
const keyMap = { event.preventDefault();
'+': 'add',
'-': 'sub',
'*': 'mul',
'/': 'div',
'%': 'mod',
'^': 'pow',
};
if (keyMap[event.key]) { if (action.type === 'input') {
event.preventDefault(); editXWithKey(action.value);
execute(keyMap[event.key]); render();
return;
}
if (action.type === 'stackOrEdit') {
if (calc.isEditing) {
editXWithKey('Backspace');
render();
} else {
execute(action.value);
}
return;
}
if (action.type === 'command') {
execute(action.value);
} }
} catch (error) { } catch (error) {
errorEl.textContent = error.message; errorEl.textContent = error.message;
} }
}); }
window.addEventListener('keydown', handleKeydown);
screen.addEventListener('click', focusScreen); screen.addEventListener('click', focusScreen);
window.addEventListener('load', focusScreen); window.addEventListener('load', focusScreen);