use crate::keys::{Arrow, KeyInput, Modifier};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Key {
Char(char, char),
Modifier(Modifier),
Enter,
Backspace,
Tab,
}
#[rustfmt::skip]
const LAYOUT: [[Key; 14]; 4] = {
use Key::{Backspace, Char as C, Enter, Modifier as M, Tab};
use Modifier::{Alt, Ctrl, Fn, Opt, Shift};
[
[C('`', '~'), C('1', '!'), C('2', '@'), C('3', '#'), C('4', '$'), C('5', '%'), C('6', '^'),
C('7', '&'), C('8', '*'), C('9', '('), C('0', ')'), C('-', '_'), C('=', '+'), Backspace],
[Tab, C('q', 'Q'), C('w', 'W'), C('e', 'E'), C('r', 'R'), C('t', 'T'), C('y', 'Y'),
C('u', 'U'), C('i', 'I'), C('o', 'O'), C('p', 'P'), C('[', '{'), C(']', '}'), C('\\', '|')],
[M(Fn), M(Shift), C('a', 'A'), C('s', 'S'), C('d', 'D'), C('f', 'F'), C('g', 'G'),
C('h', 'H'), C('j', 'J'), C('k', 'K'), C('l', 'L'), C(';', ':'), C('\'', '"'), Enter],
[M(Ctrl), M(Opt), M(Alt), C('z', 'Z'), C('x', 'X'), C('c', 'C'), C('v', 'V'),
C('b', 'B'), C('n', 'N'), C('m', 'M'), C(',', '<'), C('.', '>'), C('/', '?'), C(' ', ' ')],
]
};
fn key(number: u8) -> Option<Key> {
let index = number.checked_sub(1).filter(|&index| index < 80)?;
let (row, column) = (usize::from(index / 10), usize::from(index % 10));
if column >= 8 {
return None;
}
LAYOUT
.get(column % 4)?
.get(row * 2 + usize::from(column >= 4))
.copied()
}
const fn fn_layer(key: Key) -> Option<KeyInput> {
Some(match key {
Key::Char('`', _) => KeyInput::Escape,
Key::Char('\\', _) | Key::Backspace => KeyInput::Delete,
Key::Char(';', _) => KeyInput::Arrow(Arrow::Up),
Key::Char(',', _) => KeyInput::Arrow(Arrow::Left),
Key::Char('.', _) => KeyInput::Arrow(Arrow::Down),
Key::Char('/', _) => KeyInput::Arrow(Arrow::Right),
_ => return None,
})
}
const fn typed(key: Key, shift: bool, fn_held: bool) -> KeyInput {
if fn_held && let Some(input) = fn_layer(key) {
return input;
}
match key {
Key::Char(plain, shifted) => KeyInput::Char(if shift { shifted } else { plain }),
Key::Modifier(modifier) => KeyInput::Modifier(modifier),
Key::Enter => KeyInput::Enter,
Key::Backspace => KeyInput::Backspace,
Key::Tab => KeyInput::Tab,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Keymap {
held: u128,
}
impl Keymap {
#[must_use]
pub const fn new() -> Self {
Self { held: 0 }
}
pub fn input(&mut self, event: u8) -> Option<KeyInput> {
let number = event & 0x7F;
let key = key(number)?;
let pressed = event & 0x80 != 0;
if pressed {
self.held |= 1 << number;
} else {
self.held &= !(1 << number);
}
pressed.then(|| {
typed(
key,
self.holds(Key::Modifier(Modifier::Shift)),
self.holds(Key::Modifier(Modifier::Fn)),
)
})
}
#[must_use]
pub fn is_held(&self, input: KeyInput) -> bool {
self.held_keys().any(|key| {
[(false, false), (true, false), (false, true)]
.into_iter()
.any(|(shift, fn_held)| typed(key, shift, fn_held) == input)
})
}
fn holds(&self, key: Key) -> bool {
self.held_keys().any(|held| held == key)
}
fn held_keys(&self) -> impl Iterator<Item = Key> + use<'_> {
(1..=80)
.filter(|&number| self.held & 1 << number != 0)
.filter_map(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
const fn press(number: u8) -> u8 {
number | 0x80
}
const FN: u8 = 3;
const SHIFT: u8 = 7;
const A: u8 = 13;
const SEMICOLON: u8 = 57;
#[test]
fn a_key_types_its_character_and_shift_the_upper_one() {
let mut keymap = Keymap::default();
assert_eq!(keymap.input(press(A)), Some(KeyInput::Char('a')));
assert_eq!(keymap.input(A), None);
assert_eq!(
keymap.input(press(SHIFT)),
Some(KeyInput::Modifier(Modifier::Shift))
);
assert_eq!(keymap.input(press(A)), Some(KeyInput::Char('A')));
assert_eq!(keymap.input(SHIFT), None);
assert_eq!(keymap.input(press(A)), Some(KeyInput::Char('a')));
}
#[test]
fn fn_gives_the_arrows_over_shift() {
let mut keymap = Keymap::default();
keymap.input(press(FN));
keymap.input(press(SHIFT));
assert_eq!(
keymap.input(press(SEMICOLON)),
Some(KeyInput::Arrow(Arrow::Up))
);
assert_eq!(keymap.input(press(A)), Some(KeyInput::Char('A')));
}
#[test]
fn a_key_is_held_from_its_press_to_its_release() {
let mut keymap = Keymap::default();
assert!(!keymap.is_held(KeyInput::Char('a')));
keymap.input(press(A));
assert!(keymap.is_held(KeyInput::Char('a')));
assert!(keymap.is_held(KeyInput::Char('A')), "the same key");
assert!(!keymap.is_held(KeyInput::Char('s')));
keymap.input(A);
assert!(!keymap.is_held(KeyInput::Char('a')));
}
#[test]
fn modifiers_and_fn_layer_keys_are_held_too() {
let mut keymap = Keymap::default();
keymap.input(press(FN));
keymap.input(press(SEMICOLON));
assert!(keymap.is_held(KeyInput::Modifier(Modifier::Fn)));
assert!(keymap.is_held(KeyInput::Arrow(Arrow::Up)));
assert!(keymap.is_held(KeyInput::Char(';')), "the same key");
assert!(!keymap.is_held(KeyInput::Modifier(Modifier::Shift)));
keymap.input(FN);
assert!(keymap.is_held(KeyInput::Arrow(Arrow::Up)));
}
#[test]
fn numbers_off_the_keyboard_type_nothing() {
let mut keymap = Keymap::default();
assert_eq!(keymap.input(0), None);
assert_eq!(keymap.input(press(0)), None);
assert_eq!(keymap.input(press(81)), None);
assert_eq!(keymap.input(press(97)), None);
assert_eq!(keymap.input(press(71)), None);
assert_eq!(keymap.input(press(9)), None);
}
#[test]
fn every_layout_position_has_one_key_number() {
let found = (1..=80).filter(|&number| key(number).is_some()).count();
assert_eq!(found, 56);
}
}