#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum Layout {
#[default]
Hp48,
Hp49,
Hp38,
Hp39,
Hp42,
}
impl Layout {
pub fn in_mask(self) -> u8 {
match self {
Layout::Hp48 | Layout::Hp38 => 0x3F,
Layout::Hp42 => 0x7F,
Layout::Hp49 | Layout::Hp39 => 0xFF,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum KeyPos {
Matrix {
out: usize,
mask: u8,
},
On,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[allow(missing_docs)]
pub enum Key {
A,
B,
C,
D,
E,
F,
Prg,
Cst,
Var,
Up,
Nxt,
Sto,
Eval,
Left,
Down,
Right,
Cos,
Tan,
Sqrt,
Power,
Inv,
Enter,
Neg,
Eex,
Del,
Backspace,
Alpha,
Sin,
Seven,
Eight,
Nine,
Divide,
LeftShift,
Mth,
Four,
Five,
Six,
Multiply,
RightShift,
One,
Two,
Three,
Minus,
Quote,
Zero,
Point,
Space,
Plus,
Apps,
Mode,
Tool,
Hist,
Cat,
Eqw,
Symb,
X,
Plot,
Num,
Lib,
Math,
Home,
Xt,
LParen,
RParen,
Shift,
Comma,
Aplet,
Views,
Vars,
Ddx,
Ln,
Log,
Square,
SigmaPlus,
Xeq,
Rcl,
RollDown,
Swap,
Rs,
On,
}
const ON_IN_BIT: u16 = 0x8000;
impl Key {
pub const ALL: [Key; 80] = [
Key::A,
Key::B,
Key::C,
Key::D,
Key::E,
Key::F,
Key::Prg,
Key::Cst,
Key::Var,
Key::Up,
Key::Nxt,
Key::Sto,
Key::Eval,
Key::Left,
Key::Down,
Key::Right,
Key::Cos,
Key::Tan,
Key::Sqrt,
Key::Power,
Key::Inv,
Key::Enter,
Key::Neg,
Key::Eex,
Key::Del,
Key::Backspace,
Key::Alpha,
Key::Sin,
Key::Seven,
Key::Eight,
Key::Nine,
Key::Divide,
Key::LeftShift,
Key::Mth,
Key::Four,
Key::Five,
Key::Six,
Key::Multiply,
Key::RightShift,
Key::One,
Key::Two,
Key::Three,
Key::Minus,
Key::Quote,
Key::Zero,
Key::Point,
Key::Space,
Key::Plus,
Key::Apps,
Key::Mode,
Key::Tool,
Key::Hist,
Key::Cat,
Key::Eqw,
Key::Symb,
Key::X,
Key::Plot,
Key::Num,
Key::Lib,
Key::Math,
Key::Home,
Key::Xt,
Key::LParen,
Key::RParen,
Key::Shift,
Key::Comma,
Key::Aplet,
Key::Views,
Key::Vars,
Key::Ddx,
Key::Ln,
Key::Log,
Key::Square,
Key::SigmaPlus,
Key::Xeq,
Key::Rcl,
Key::RollDown,
Key::Swap,
Key::Rs,
Key::On,
];
pub(crate) fn position(self, layout: Layout) -> Option<KeyPos> {
match layout {
Layout::Hp48 => position_48(self),
Layout::Hp49 => super::keyboard49::position(self),
Layout::Hp38 => super::keyboard_aplet::as_48_key(self).and_then(position_48),
Layout::Hp39 => {
super::keyboard_aplet::as_49_key(self).and_then(super::keyboard49::position)
}
Layout::Hp42 => super::keyboard42::position(self),
}
}
pub(crate) fn on_layout(layout: Layout) -> impl Iterator<Item = Key> {
Key::ALL
.into_iter()
.filter(move |k| k.position(layout).is_some())
}
pub fn name(self) -> &'static str {
match self {
Key::A => "a",
Key::B => "b",
Key::C => "c",
Key::D => "d",
Key::E => "e",
Key::F => "f",
Key::Prg => "prg",
Key::Cst => "cst",
Key::Var => "var",
Key::Up => "up",
Key::Nxt => "nxt",
Key::Sto => "sto",
Key::Eval => "eval",
Key::Left => "left",
Key::Down => "down",
Key::Right => "right",
Key::Cos => "cos",
Key::Tan => "tan",
Key::Sqrt => "sqrt",
Key::Power => "power",
Key::Inv => "inv",
Key::Enter => "enter",
Key::Neg => "neg",
Key::Eex => "eex",
Key::Del => "del",
Key::Backspace => "backspace",
Key::Alpha => "alpha",
Key::Sin => "sin",
Key::Seven => "7",
Key::Eight => "8",
Key::Nine => "9",
Key::Divide => "divide",
Key::LeftShift => "leftshift",
Key::Mth => "mth",
Key::Four => "4",
Key::Five => "5",
Key::Six => "6",
Key::Multiply => "multiply",
Key::RightShift => "rightshift",
Key::One => "1",
Key::Two => "2",
Key::Three => "3",
Key::Minus => "minus",
Key::Quote => "quote",
Key::Zero => "0",
Key::Point => "point",
Key::Space => "space",
Key::Plus => "plus",
Key::Apps => "apps",
Key::Mode => "mode",
Key::Tool => "tool",
Key::Hist => "hist",
Key::Cat => "cat",
Key::Eqw => "eqw",
Key::Symb => "symb",
Key::X => "x",
Key::Plot => "plot",
Key::Num => "num",
Key::Lib => "lib",
Key::Math => "math",
Key::Home => "home",
Key::Xt => "xt",
Key::LParen => "lparen",
Key::RParen => "rparen",
Key::Shift => "shift",
Key::Comma => "comma",
Key::Aplet => "aplet",
Key::Views => "views",
Key::Vars => "vars",
Key::Ddx => "ddx",
Key::Ln => "ln",
Key::Log => "log",
Key::Square => "square",
Key::SigmaPlus => "sigmaplus",
Key::Xeq => "xeq",
Key::Rcl => "rcl",
Key::RollDown => "rdn",
Key::Swap => "swap",
Key::Rs => "rs",
Key::On => "on",
}
}
pub fn from_name(name: &str) -> Option<Key> {
const SOFTKEYS: [(&str, Key); 7] = [
("f1", Key::A),
("f2", Key::B),
("f3", Key::C),
("f4", Key::D),
("f5", Key::E),
("f6", Key::F),
("exit", Key::On),
];
Key::ALL
.iter()
.copied()
.find(|k| k.name().eq_ignore_ascii_case(name))
.or_else(|| {
SOFTKEYS
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|&(_, k)| k)
})
}
}
pub(crate) fn position_48(key: Key) -> Option<KeyPos> {
let (out, mask) = match key {
Key::B => (8, 0x10),
Key::C => (8, 0x08),
Key::D => (8, 0x04),
Key::E => (8, 0x02),
Key::F => (8, 0x01),
Key::Prg => (7, 0x10),
Key::Cst => (7, 0x08),
Key::Var => (7, 0x04),
Key::Up => (7, 0x02),
Key::Nxt => (7, 0x01),
Key::Sto => (6, 0x10),
Key::Eval => (6, 0x08),
Key::Left => (6, 0x04),
Key::Down => (6, 0x02),
Key::Right => (6, 0x01),
Key::Cos => (5, 0x10),
Key::Tan => (5, 0x08),
Key::Sqrt => (5, 0x04),
Key::Power => (5, 0x02),
Key::Inv => (5, 0x01),
Key::Enter => (4, 0x10),
Key::Neg => (4, 0x08),
Key::Eex => (4, 0x04),
Key::Del => (4, 0x02),
Key::Backspace => (4, 0x01),
Key::Alpha => (3, 0x20),
Key::Sin => (3, 0x10),
Key::Seven => (3, 0x08),
Key::Eight => (3, 0x04),
Key::Nine => (3, 0x02),
Key::Divide => (3, 0x01),
Key::LeftShift => (2, 0x20),
Key::Mth => (2, 0x10),
Key::Four => (2, 0x08),
Key::Five => (2, 0x04),
Key::Six => (2, 0x02),
Key::Multiply => (2, 0x01),
Key::RightShift => (1, 0x20),
Key::A => (1, 0x10),
Key::One => (1, 0x08),
Key::Two => (1, 0x04),
Key::Three => (1, 0x02),
Key::Minus => (1, 0x01),
Key::Quote => (0, 0x10),
Key::Zero => (0, 0x08),
Key::Point => (0, 0x04),
Key::Space => (0, 0x02),
Key::Plus => (0, 0x01),
Key::On => return Some(KeyPos::On),
Key::Apps
| Key::Mode
| Key::Tool
| Key::Hist
| Key::Cat
| Key::Eqw
| Key::Symb
| Key::X
| Key::Plot
| Key::Num
| Key::Lib
| Key::Math
| Key::Home
| Key::Xt
| Key::LParen
| Key::RParen
| Key::Shift
| Key::Comma
| Key::Aplet
| Key::Views
| Key::Vars
| Key::Ddx
| Key::Ln
| Key::Log
| Key::Square
| Key::SigmaPlus
| Key::Xeq
| Key::Rcl
| Key::RollDown
| Key::Swap
| Key::Rs => return None,
};
Some(KeyPos::Matrix { out, mask })
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Keyboard {
pub(crate) layout: Layout,
pub(crate) rows: [u8; 9],
pub(crate) on: bool,
}
impl Keyboard {
#[cfg(test)]
pub fn new() -> Self {
Self::default()
}
pub fn with_layout(layout: Layout) -> Self {
Self {
layout,
..Self::default()
}
}
pub fn layout(&self) -> Layout {
self.layout
}
pub fn press(&mut self, k: Key) -> bool {
match k.position(self.layout) {
Some(KeyPos::Matrix { out, mask }) => self.rows[out] |= mask,
Some(KeyPos::On) => self.on = true,
None => return false,
}
true
}
pub fn release(&mut self, k: Key) -> bool {
match k.position(self.layout) {
Some(KeyPos::Matrix { out, mask }) => self.rows[out] &= !mask,
Some(KeyPos::On) => self.on = false,
None => return false,
}
true
}
pub fn release_all(&mut self) {
*self = Self::with_layout(self.layout);
}
pub fn is_pressed(&self, k: Key) -> bool {
match k.position(self.layout) {
Some(KeyPos::Matrix { out, mask }) => self.rows[out] & mask != 0,
Some(KeyPos::On) => self.on,
None => false,
}
}
pub fn on_pressed(&self) -> bool {
self.on
}
pub fn read_in(&self, out: u16) -> u16 {
let mut value = self
.rows
.iter()
.enumerate()
.filter(|(i, _)| out & (1 << i) != 0)
.fold(0u16, |acc, (_, row)| acc | u16::from(*row));
if self.on {
value |= ON_IN_BIT;
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enter_row() {
let mut kb = Keyboard::new();
kb.press(Key::Enter);
assert_eq!(kb.read_in(0x010), 0x0010);
assert_eq!(kb.read_in(0x1FF), 0x0010);
assert_eq!(kb.read_in(0x001), 0);
kb.release(Key::Enter);
assert_eq!(kb.read_in(0x1FF), 0);
}
#[test]
fn on_ignores_out() {
let mut kb = Keyboard::new();
kb.press(Key::On);
assert_eq!(kb.read_in(0), 0x8000);
assert!(kb.on_pressed());
kb.release_all();
assert_eq!(kb.read_in(0x1FF), 0);
}
#[test]
fn names_round_trip() {
assert_eq!(Key::from_name("F1"), Some(Key::A));
assert_eq!(Key::from_name("f6"), Some(Key::F));
let names: std::collections::HashSet<_> = Key::ALL.iter().map(|k| k.name()).collect();
assert_eq!(names.len(), Key::ALL.len());
for k in Key::ALL {
assert_eq!(Key::from_name(k.name()), Some(k));
assert_eq!(Key::from_name(&k.name().to_uppercase()), Some(k));
}
assert_eq!(Key::from_name("7"), Some(Key::Seven));
assert_eq!(Key::from_name("LeftShift"), Some(Key::LeftShift));
assert_eq!(Key::from_name("nope"), None);
}
const WIKI_MATRIX: [(Key, u16, u16); 49] = [
(Key::B, 0x100, 0x10),
(Key::C, 0x100, 0x08),
(Key::D, 0x100, 0x04),
(Key::E, 0x100, 0x02),
(Key::F, 0x100, 0x01),
(Key::Prg, 0x080, 0x10),
(Key::Cst, 0x080, 0x08),
(Key::Var, 0x080, 0x04),
(Key::Up, 0x080, 0x02),
(Key::Nxt, 0x080, 0x01),
(Key::Sto, 0x040, 0x10),
(Key::Eval, 0x040, 0x08),
(Key::Left, 0x040, 0x04),
(Key::Down, 0x040, 0x02),
(Key::Right, 0x040, 0x01),
(Key::Cos, 0x020, 0x10),
(Key::Tan, 0x020, 0x08),
(Key::Sqrt, 0x020, 0x04),
(Key::Power, 0x020, 0x02),
(Key::Inv, 0x020, 0x01),
(Key::Enter, 0x010, 0x10),
(Key::Neg, 0x010, 0x08),
(Key::Eex, 0x010, 0x04),
(Key::Del, 0x010, 0x02),
(Key::Backspace, 0x010, 0x01),
(Key::Alpha, 0x008, 0x20),
(Key::Sin, 0x008, 0x10),
(Key::Seven, 0x008, 0x08),
(Key::Eight, 0x008, 0x04),
(Key::Nine, 0x008, 0x02),
(Key::Divide, 0x008, 0x01),
(Key::LeftShift, 0x004, 0x20),
(Key::Mth, 0x004, 0x10),
(Key::Four, 0x004, 0x08),
(Key::Five, 0x004, 0x04),
(Key::Six, 0x004, 0x02),
(Key::Multiply, 0x004, 0x01),
(Key::RightShift, 0x002, 0x20),
(Key::A, 0x002, 0x10),
(Key::One, 0x002, 0x08),
(Key::Two, 0x002, 0x04),
(Key::Three, 0x002, 0x02),
(Key::Minus, 0x002, 0x01),
(Key::Quote, 0x001, 0x10),
(Key::Zero, 0x001, 0x08),
(Key::Point, 0x001, 0x04),
(Key::Space, 0x001, 0x02),
(Key::Plus, 0x001, 0x01),
(Key::On, 0x000, 0x8000),
];
#[test]
fn every_key_matches_the_wiki_matrix() {
let listed: Vec<Key> = WIKI_MATRIX.iter().map(|e| e.0).collect();
let mut on_48: Vec<Key> = Key::on_layout(Layout::Hp48).collect();
let mut listed = listed;
listed.sort_by_key(|k| k.name());
on_48.sort_by_key(|k| k.name());
assert_eq!(listed, on_48, "table covers exactly the 48 keys");
for (key, out, inp) in WIKI_MATRIX {
let mut kb = Keyboard::new();
kb.press(key);
assert_eq!(kb.read_in(out), inp, "{key:?} on its own row");
for bit in 0..12 {
let other = 1u16 << bit;
if other == out {
continue;
}
let want = if key == Key::On { 0x8000 } else { 0 };
assert_eq!(kb.read_in(other), want, "{key:?} with OUT {other:#05X}");
}
assert_eq!(kb.read_in(0x1FF), inp);
assert_eq!(kb.read_in(0x9FF), inp);
assert_eq!(kb.read_in(0x800), if key == Key::On { inp } else { 0 });
}
}
#[test]
fn matrix_positions_unique() {
let positions: std::collections::HashSet<_> = Key::ALL
.iter()
.filter_map(|k| k.position(Layout::Hp48))
.collect();
assert_eq!(positions.len(), 49, "48 matrix cells and ON");
let mut kb = Keyboard::new();
for k in Key::on_layout(Layout::Hp48) {
assert!(!kb.is_pressed(k));
kb.press(k);
assert!(kb.is_pressed(k));
}
assert_eq!(kb.read_in(0x1FF), 0x803F);
kb.release_all();
assert!(!kb.press(Key::Apps), "APPS is not on the 48");
assert!(!kb.release(Key::Apps));
assert!(!kb.is_pressed(Key::Apps));
assert_eq!(kb.read_in(0x1FF), 0);
}
}