use rmk_types::steno::StenoKey;
use crate::hid::{Report, StenoReport};
#[derive(Debug, Default)]
pub(crate) struct StenoChord {
state: u64,
}
impl StenoChord {
pub(crate) const fn new() -> Self {
Self { state: 0 }
}
pub(crate) fn on_event(&mut self, key: StenoKey, pressed: bool) -> Option<Report> {
let idx = key.chart_index();
if idx >= 64 {
return None;
}
let mask = 1u64 << (63 - idx);
if pressed {
self.state |= mask;
} else {
self.state &= !mask;
}
Some(Report::StenoReport(StenoReport {
keys: self.state.to_be_bytes(),
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn report_bytes(report: Option<Report>) -> [u8; 8] {
match report.expect("expected a report") {
Report::StenoReport(r) => r.keys,
_ => panic!("expected StenoReport"),
}
}
fn bitmap(report: Option<Report>) -> u64 {
u64::from_be_bytes(report_bytes(report))
}
fn msb_bit(key: StenoKey) -> u64 {
1u64 << (63 - key.chart_index())
}
#[test]
fn press_sends_live_state() {
let mut s = StenoChord::new();
let b = bitmap(s.on_event(StenoKey::S1, true));
assert_eq!(b, msb_bit(StenoKey::S1));
let b = bitmap(s.on_event(StenoKey::T, true));
assert_eq!(b, msb_bit(StenoKey::S1) | msb_bit(StenoKey::T));
}
#[test]
fn release_clears_bit() {
let mut s = StenoChord::new();
s.on_event(StenoKey::S1, true);
s.on_event(StenoKey::T, true);
let b = bitmap(s.on_event(StenoKey::T, false));
assert_eq!(b, msb_bit(StenoKey::S1), "T bit should be cleared");
let b = bitmap(s.on_event(StenoKey::S1, false));
assert_eq!(b, 0, "all bits should be cleared");
}
#[test]
fn every_event_produces_a_report() {
let mut s = StenoChord::new();
assert!(s.on_event(StenoKey::S1, true).is_some());
assert!(s.on_event(StenoKey::T, true).is_some());
assert!(s.on_event(StenoKey::T, false).is_some());
assert!(s.on_event(StenoKey::S1, false).is_some());
}
#[test]
fn s1_is_msb_of_first_byte() {
let mut s = StenoChord::new();
let bytes = report_bytes(s.on_event(StenoKey::S1, true));
assert_eq!(bytes[0], 0x80);
assert_eq!(&bytes[1..], &[0; 7]);
}
#[test]
fn x26_is_lsb_of_last_byte() {
let mut s = StenoChord::new();
let bytes = report_bytes(s.on_event(StenoKey::X26, true));
assert_eq!(&bytes[..7], &[0; 7]);
assert_eq!(bytes[7], 0x01);
}
#[test]
fn out_of_range_index_returns_none() {
let mut s = StenoChord::new();
assert!(s.on_event(StenoKey(64), true).is_none());
}
#[test]
fn repeated_chord_produces_zero_between() {
let mut s = StenoChord::new();
for _ in 0..3 {
s.on_event(StenoKey::S1, true);
s.on_event(StenoKey::T, true);
s.on_event(StenoKey::T, false);
let b = bitmap(s.on_event(StenoKey::S1, false));
assert_eq!(b, 0, "all-up must produce zero bitmap");
}
}
fn stroke(s: &mut StenoChord, keys: &[StenoKey]) -> u64 {
let mut peak = 0u64;
for &k in keys {
peak = bitmap(s.on_event(k, true));
}
for &k in keys {
s.on_event(k, false);
}
peak
}
#[test]
fn hello_world_strokes() {
let mut s = StenoChord::new();
let hel = stroke(&mut s, &[StenoKey::H, StenoKey::RE, StenoKey::RL]);
assert_eq!(
hel,
msb_bit(StenoKey::H) | msb_bit(StenoKey::RE) | msb_bit(StenoKey::RL)
);
let hro = stroke(&mut s, &[StenoKey::H, StenoKey::R, StenoKey::O]);
assert_eq!(hro, msb_bit(StenoKey::H) | msb_bit(StenoKey::R) | msb_bit(StenoKey::O));
let world = stroke(
&mut s,
&[StenoKey::W, StenoKey::O, StenoKey::RR, StenoKey::RL, StenoKey::RD],
);
assert_eq!(
world,
msb_bit(StenoKey::W)
| msb_bit(StenoKey::O)
| msb_bit(StenoKey::RR)
| msb_bit(StenoKey::RL)
| msb_bit(StenoKey::RD)
);
}
}