use std::{convert::Infallible, fmt::Display, str::FromStr};
use crate::{Anchor, DofError, DofErrorInner, Fingering, Keyboard, Result};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Finger {
LP,
LR,
LM,
LI,
LT,
RT,
RI,
RM,
RR,
RP,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Hand {
Left,
Right,
}
impl Finger {
pub const FINGERS: [Self; 10] = [
Self::LP,
Self::LR,
Self::LM,
Self::LI,
Self::LT,
Self::RT,
Self::RI,
Self::RM,
Self::RR,
Self::RP,
];
pub const fn is_pinky(&self) -> bool {
matches!(self, Self::LP | Self::RP)
}
pub const fn is_ring(&self) -> bool {
matches!(self, Self::LR | Self::RR)
}
pub const fn is_middle(&self) -> bool {
matches!(self, Self::LM | Self::RM)
}
pub const fn is_index(&self) -> bool {
matches!(self, Self::LI | Self::RI)
}
pub const fn is_thumb(&self) -> bool {
matches!(self, Self::LT | Self::RT)
}
pub const fn hand(&self) -> Hand {
use Finger::*;
match self {
LP | LR | LM | LI | LT => Hand::Left,
RP | RR | RM | RI | RT => Hand::Right,
}
}
pub const fn is_on_left_hand(&self) -> bool {
matches!(self.hand(), Hand::Left)
}
pub const fn is_on_right_hand(&self) -> bool {
matches!(self.hand(), Hand::Right)
}
}
impl Display for Finger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl FromStr for Finger {
type Err = DofError;
fn from_str(s: &str) -> Result<Self> {
use Finger::*;
let s = s.trim_start().trim_end();
match s {
"lp" | "LP" | "0" => Ok(LP),
"lr" | "LR" | "1" => Ok(LR),
"lm" | "LM" | "2" => Ok(LM),
"li" | "LI" | "3" => Ok(LI),
"lt" | "LT" | "4" => Ok(LT),
"rt" | "RT" | "5" => Ok(RT),
"ri" | "RI" | "6" => Ok(RI),
"rm" | "RM" | "7" => Ok(RM),
"rr" | "RR" | "8" => Ok(RR),
"rp" | "RP" | "9" => Ok(RP),
_ => Err(DofErrorInner::FingerParseError(s.to_string()).into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub enum NamedFingering {
#[default]
Traditional,
Angle,
Custom(String),
}
impl Display for NamedFingering {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Traditional => "traditional",
Self::Angle => "angle",
Self::Custom(name) => name.as_str(),
};
write!(f, "{s}")
}
}
impl FromStr for NamedFingering {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let res = match s.to_lowercase().as_str() {
"standard" | "traditional" => Self::Traditional,
"angle" => Self::Angle,
name => Self::Custom(name.into()),
};
Ok(res)
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SpecialKey {
Esc,
Repeat,
Space,
Tab,
Enter,
Shift,
Caps,
Ctrl,
Alt,
Meta,
Menu,
Fn,
Backspace,
Del,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Key {
#[default]
Empty,
Transparent,
Char(char),
Word(String),
Special(SpecialKey),
Layer {
label: String,
},
Magic {
label: String,
},
}
impl Key {
pub fn shifted(&self) -> Self {
use Key::*;
match self {
Char(c) => match c {
'`' => Char('~'),
'1' => Char('!'),
'2' => Char('@'),
'3' => Char('#'),
'4' => Char('$'),
'5' => Char('%'),
'6' => Char('^'),
'7' => Char('*'),
'9' => Char('('),
'0' => Char(')'),
'[' => Char('{'),
']' => Char('}'),
'<' => Char('>'),
'\'' => Char('"'),
',' => Char('<'),
'.' => Char('>'),
';' => Char(':'),
'/' => Char('?'),
'=' => Char('+'),
'-' => Char('_'),
'\\' => Char('|'),
c => {
let mut upper = c.to_uppercase();
if upper.clone().count() == 1 {
Char(upper.next().unwrap())
} else {
Word(upper.to_string())
}
}
},
Special(_) => Transparent,
k => k.clone(),
}
}
pub const fn is_char(&self) -> bool {
matches!(self, Key::Char(_))
}
pub const fn is_word(&self) -> bool {
matches!(self, Key::Word(_))
}
pub const fn is_empty(&self) -> bool {
matches!(self, Key::Empty)
}
pub const fn is_transparent(&self) -> bool {
matches!(self, Key::Transparent)
}
pub const fn is_layer(&self) -> bool {
matches!(self, Key::Layer { label: _ })
}
pub const fn is_magic(&self) -> bool {
matches!(self, Key::Magic { label: _ })
}
pub const fn char_output(&self) -> Option<char> {
match self {
Key::Char(c) => Some(*c),
_ => None,
}
}
pub fn word_output(&self) -> Option<&str> {
match &self {
Key::Word(s) => Some(s),
_ => None,
}
}
pub fn layer_label(&self) -> Option<&str> {
match &self {
Key::Layer { label } => Some(label),
_ => None,
}
}
pub fn magic_label(&self) -> Option<&str> {
match &self {
Key::Magic { label } => Some(label),
_ => None,
}
}
}
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Key::*;
use SpecialKey::*;
let s = match self {
Empty => "~".into(),
Transparent => "*".into(),
Char(c) => match c {
n @ ('~' | '*') => format!("\\{n}"),
n => String::from(*n),
},
Word(w) => w.clone(),
Special(s) => match s {
Esc => "esc".into(),
Repeat => "rpt".into(),
Space => "spc".into(),
Tab => "tab".into(),
Enter => "ret".into(),
Shift => "sft".into(),
Caps => "cps".into(),
Ctrl => "ctl".into(),
Alt => "alt".into(),
Meta => "mt".into(),
Menu => "mn".into(),
Fn => "fn".into(),
Backspace => "bsp".into(),
Del => "del".into(),
},
Layer { label } => format!("@{label}"),
Magic { label } => format!("&{label}"),
};
write!(f, "{s}")
}
}
impl FromStr for Key {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(s.into())
}
}
impl<T> From<T> for Key
where
T: AsRef<str>,
{
fn from(value: T) -> Self {
use Key::*;
use SpecialKey::*;
let s = value.as_ref();
match s.chars().count() {
0 => Empty,
1 => match s {
"~" => Empty,
"*" => Transparent,
" " => Special(Space),
"\n" => Special(Enter),
"\t" => Special(Tab),
_ => Char(s.chars().next().unwrap()),
},
_ => match s.to_lowercase().as_str() {
"\\~" => Char('~'),
"\\*" => Char('*'),
"esc" => Special(Esc),
"repeat" | "rpt" => Special(Repeat),
"space" | "spc" => Special(Space),
"tab" | "tb" => Special(Tab),
"enter" | "return" | "ret" | "ent" | "rt" => Special(Enter),
"shift" | "shft" | "sft" | "st" => Special(Shift),
"caps" | "cps" | "cp" => Special(Caps),
"ctrl" | "ctl" | "ct" => Special(Ctrl),
"alt" | "lalt" | "ralt" | "lt" => Special(Alt),
"meta" | "mta" | "met" | "mt" | "super" | "sup" | "sp" => Special(Meta),
"menu" | "mnu" | "mn" => Special(Menu),
"fn" => Special(Fn),
"backspace" | "bksp" | "bcsp" | "bsp" => Special(Backspace),
"del" => Special(Del),
_ if s.starts_with('@') => Layer {
label: s.chars().skip(1).collect(),
},
_ if s.starts_with('&') => Magic {
label: s.chars().skip(1).collect(),
},
_ if s.starts_with('#')
|| s.starts_with("\\#")
|| s.starts_with("\\@")
|| s.starts_with("\\&") =>
{
Word(s.chars().skip(1).collect())
}
_ => Word(s.into()),
},
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Shape(Vec<usize>);
impl From<Vec<usize>> for Shape {
fn from(value: Vec<usize>) -> Self {
Shape(value)
}
}
impl<const N: usize> From<[usize; N]> for Shape {
fn from(value: [usize; N]) -> Self {
Shape(value.into())
}
}
impl Shape {
pub fn inner(&self) -> &[usize] {
&self.0
}
pub fn into_inner(self) -> Vec<usize> {
self.0
}
pub fn row_count(&self) -> usize {
self.0.len()
}
fn rows(&self) -> impl Iterator<Item = &usize> {
self.inner().iter()
}
pub fn fits_in(&self, cmp: &Self) -> bool {
if self.row_count() > cmp.row_count() {
false
} else {
self.rows().zip(cmp.rows()).all(|(s, c)| s <= c)
}
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FormFactor {
Ansi,
Iso,
Ortho,
Colstag,
Custom(String),
}
impl FormFactor {
pub fn shape(&self) -> Shape {
self.fingering(&NamedFingering::Traditional)
.unwrap()
.shape()
}
pub fn fingering(&self, named_fingering: &NamedFingering) -> Result<Fingering> {
use Finger::*;
use FormFactor::*;
use NamedFingering::*;
let fingering = match (self, &named_fingering) {
(Ansi, Traditional) => vec![
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP],
vec![LP, LP, LT, LT, RT, RT, RP, RP],
]
.into(),
(Ansi, Angle) => vec![
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
vec![LP, LR, LM, LI, LI, LI, RI, RI, RM, RR, RP, RP],
vec![LP, LP, LT, LT, RT, RT, RP, RP],
]
.into(),
(Iso, Traditional) => vec![
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
vec![LP, LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP],
vec![LP, LP, LT, LT, RT, RT, RP, RP],
]
.into(),
(Iso, Angle) => vec![
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
vec![LP, LP, LR, LM, LI, LI, LI, RI, RI, RM, RR, RP, RP],
vec![LP, LP, LT, LT, RT, RT, RP, RP],
]
.into(),
(Ortho, Traditional) => vec![
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LT, LT, LT, RT, RT, RT],
]
.into(),
(Colstag, Traditional) => vec![
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
vec![LT, LT, LT, RT, RT, RT],
]
.into(),
(board, &f) => {
return Err(DofErrorInner::UnsupportedKeyboardFingeringCombo(
board.clone(),
f.clone(),
)
.into());
}
};
Ok(fingering)
}
pub const fn is_custom(&self) -> bool {
matches!(self, Self::Custom(_))
}
pub const fn anchor(&self) -> Anchor {
use FormFactor::*;
match self {
Ansi => Anchor::new(1, 1),
Iso => Anchor::new(1, 1),
Ortho => Anchor::new(0, 0),
Colstag => Anchor::new(0, 0),
Custom(_) => Anchor::new(0, 0),
}
}
}
impl Display for FormFactor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use FormFactor::*;
let s = match self {
Ansi => "ansi",
Iso => "iso",
Ortho => "ortho",
Colstag => "colstag",
Custom(name) => name.as_str(),
};
write!(f, "{s}")
}
}
impl FromStr for FormFactor {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
use FormFactor::*;
match s.to_lowercase().as_str() {
"ansi" => Ok(Ansi),
"iso" => Ok(Iso),
"ortho" => Ok(Ortho),
"colstag" => Ok(Colstag),
name => Ok(Custom(name.into())),
}
}
}