1use std::{convert::Infallible, fmt::Display, str::FromStr};
4
5use crate::{Anchor, DofError, DofErrorInner, Fingering, Keyboard, Result};
6
7#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
11pub enum Finger {
12 LP,
14 LR,
16 LM,
18 LI,
20 LT,
22 RT,
24 RI,
26 RM,
28 RR,
30 RP,
32}
33
34#[derive(Debug, Copy, Clone, PartialEq)]
36pub enum Hand {
37 Left,
39 Right,
41}
42
43impl Finger {
44 pub const FINGERS: [Self; 10] = [
46 Self::LP,
47 Self::LR,
48 Self::LM,
49 Self::LI,
50 Self::LT,
51 Self::RT,
52 Self::RI,
53 Self::RM,
54 Self::RR,
55 Self::RP,
56 ];
57
58 pub const fn is_pinky(&self) -> bool {
60 matches!(self, Self::LP | Self::RP)
61 }
62
63 pub const fn is_ring(&self) -> bool {
65 matches!(self, Self::LR | Self::RR)
66 }
67
68 pub const fn is_middle(&self) -> bool {
70 matches!(self, Self::LM | Self::RM)
71 }
72
73 pub const fn is_index(&self) -> bool {
75 matches!(self, Self::LI | Self::RI)
76 }
77
78 pub const fn is_thumb(&self) -> bool {
80 matches!(self, Self::LT | Self::RT)
81 }
82
83 pub const fn hand(&self) -> Hand {
85 use Finger::*;
86
87 match self {
88 LP | LR | LM | LI | LT => Hand::Left,
89 RP | RR | RM | RI | RT => Hand::Right,
90 }
91 }
92
93 pub const fn is_on_left_hand(&self) -> bool {
95 matches!(self.hand(), Hand::Left)
96 }
97
98 pub const fn is_on_right_hand(&self) -> bool {
100 matches!(self.hand(), Hand::Right)
101 }
102}
103
104impl Display for Finger {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 write!(f, "{self:?}")
107 }
108}
109
110impl FromStr for Finger {
111 type Err = DofError;
112
113 fn from_str(s: &str) -> Result<Self> {
114 use Finger::*;
115
116 let s = s.trim_start().trim_end();
117 match s {
118 "lp" | "LP" | "0" => Ok(LP),
119 "lr" | "LR" | "1" => Ok(LR),
120 "lm" | "LM" | "2" => Ok(LM),
121 "li" | "LI" | "3" => Ok(LI),
122 "lt" | "LT" | "4" => Ok(LT),
123 "rt" | "RT" | "5" => Ok(RT),
124 "ri" | "RI" | "6" => Ok(RI),
125 "rm" | "RM" | "7" => Ok(RM),
126 "rr" | "RR" | "8" => Ok(RR),
127 "rp" | "RP" | "9" => Ok(RP),
128 _ => Err(DofErrorInner::FingerParseError(s.to_string()).into()),
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
137pub enum NamedFingering {
138 #[default]
140 Traditional,
141 Angle,
143 Custom(String),
145}
146
147impl Display for NamedFingering {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 let s = match self {
150 Self::Traditional => "traditional",
151 Self::Angle => "angle",
152 Self::Custom(name) => name.as_str(),
153 };
154
155 write!(f, "{s}")
156 }
157}
158
159impl FromStr for NamedFingering {
160 type Err = std::convert::Infallible;
161
162 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
163 let res = match s.to_lowercase().as_str() {
164 "standard" | "traditional" => Self::Traditional,
165 "angle" => Self::Angle,
166 name => Self::Custom(name.into()),
167 };
168
169 Ok(res)
170 }
171}
172
173#[allow(missing_docs)]
176#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
177pub enum SpecialKey {
178 Esc,
179 Repeat,
180 Space,
181 Tab,
182 Enter,
183 Shift,
184 Caps,
185 Ctrl,
186 Alt,
187 Meta,
188 Menu,
189 Fn,
190 Backspace,
191 Del,
192}
193
194#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
212pub enum Key {
213 #[default]
214 Empty,
216 Transparent,
218 Char(char),
220 Word(String),
222 Special(SpecialKey),
224 Layer {
226 label: String,
228 },
229 Magic {
231 label: String,
233 },
234}
235
236impl Key {
237 pub fn shifted(&self) -> Self {
239 use Key::*;
240
241 match self {
242 Char(c) => match c {
243 '`' => Char('~'),
244 '1' => Char('!'),
245 '2' => Char('@'),
246 '3' => Char('#'),
247 '4' => Char('$'),
248 '5' => Char('%'),
249 '6' => Char('^'),
250 '7' => Char('*'),
251 '9' => Char('('),
252 '0' => Char(')'),
253 '[' => Char('{'),
254 ']' => Char('}'),
255 '<' => Char('>'),
256 '\'' => Char('"'),
257 ',' => Char('<'),
258 '.' => Char('>'),
259 ';' => Char(':'),
260 '/' => Char('?'),
261 '=' => Char('+'),
262 '-' => Char('_'),
263 '\\' => Char('|'),
264 c => {
265 let mut upper = c.to_uppercase();
266 if upper.clone().count() == 1 {
267 Char(upper.next().unwrap())
268 } else {
269 Word(upper.to_string())
270 }
271 }
272 },
273 Special(_) => Transparent,
274 k => k.clone(),
275 }
276 }
277
278 pub const fn is_char(&self) -> bool {
281 matches!(self, Key::Char(_))
282 }
283
284 pub const fn is_word(&self) -> bool {
287 matches!(self, Key::Word(_))
288 }
289
290 pub const fn is_empty(&self) -> bool {
293 matches!(self, Key::Empty)
294 }
295
296 pub const fn is_transparent(&self) -> bool {
299 matches!(self, Key::Transparent)
300 }
301
302 pub const fn is_layer(&self) -> bool {
305 matches!(self, Key::Layer { label: _ })
306 }
307
308 pub const fn is_magic(&self) -> bool {
311 matches!(self, Key::Magic { label: _ })
312 }
313
314 pub const fn char_output(&self) -> Option<char> {
316 match self {
317 Key::Char(c) => Some(*c),
318 _ => None,
319 }
320 }
321
322 pub fn word_output(&self) -> Option<&str> {
324 match &self {
325 Key::Word(s) => Some(s),
326 _ => None,
327 }
328 }
329
330 pub fn layer_label(&self) -> Option<&str> {
332 match &self {
333 Key::Layer { label } => Some(label),
334 _ => None,
335 }
336 }
337
338 pub fn magic_label(&self) -> Option<&str> {
340 match &self {
341 Key::Magic { label } => Some(label),
342 _ => None,
343 }
344 }
345}
346
347impl Display for Key {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 use Key::*;
350 use SpecialKey::*;
351
352 let s = match self {
353 Empty => "~".into(),
354 Transparent => "*".into(),
355 Char(c) => match c {
356 n @ ('~' | '*') => format!("\\{n}"),
357 n => String::from(*n),
358 },
359 Word(w) => w.clone(),
360 Special(s) => match s {
361 Esc => "esc".into(),
362 Repeat => "rpt".into(),
363 Space => "spc".into(),
364 Tab => "tab".into(),
365 Enter => "ret".into(),
366 Shift => "sft".into(),
367 Caps => "cps".into(),
368 Ctrl => "ctl".into(),
369 Alt => "alt".into(),
370 Meta => "mt".into(),
371 Menu => "mn".into(),
372 Fn => "fn".into(),
373 Backspace => "bsp".into(),
374 Del => "del".into(),
375 },
376 Layer { label } => format!("@{label}"),
377 Magic { label } => format!("&{label}"),
378 };
379
380 write!(f, "{s}")
381 }
382}
383
384impl FromStr for Key {
385 type Err = Infallible;
386
387 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
388 Ok(s.into())
389 }
390}
391
392impl<T> From<T> for Key
393where
394 T: AsRef<str>,
395{
396 fn from(value: T) -> Self {
397 use Key::*;
398 use SpecialKey::*;
399
400 let s = value.as_ref();
401
402 match s.chars().count() {
403 0 => Empty,
404 1 => match s {
405 "~" => Empty,
406 "*" => Transparent,
407 " " => Special(Space),
408 "\n" => Special(Enter),
409 "\t" => Special(Tab),
410 _ => Char(s.chars().next().unwrap()),
411 },
412 _ => match s.to_lowercase().as_str() {
413 "\\~" => Char('~'),
414 "\\*" => Char('*'),
415 "esc" => Special(Esc),
416 "repeat" | "rpt" => Special(Repeat),
417 "space" | "spc" => Special(Space),
418 "tab" | "tb" => Special(Tab),
419 "enter" | "return" | "ret" | "ent" | "rt" => Special(Enter),
420 "shift" | "shft" | "sft" | "st" => Special(Shift),
421 "caps" | "cps" | "cp" => Special(Caps),
422 "ctrl" | "ctl" | "ct" => Special(Ctrl),
423 "alt" | "lalt" | "ralt" | "lt" => Special(Alt),
424 "meta" | "mta" | "met" | "mt" | "super" | "sup" | "sp" => Special(Meta),
425 "menu" | "mnu" | "mn" => Special(Menu),
426 "fn" => Special(Fn),
427 "backspace" | "bksp" | "bcsp" | "bsp" => Special(Backspace),
428 "del" => Special(Del),
429 _ if s.starts_with('@') => Layer {
430 label: s.chars().skip(1).collect(),
431 },
432 _ if s.starts_with('&') => Magic {
433 label: s.chars().skip(1).collect(),
434 },
435 _ if s.starts_with('#')
436 || s.starts_with("\\#")
437 || s.starts_with("\\@")
438 || s.starts_with("\\&") =>
439 {
440 Word(s.chars().skip(1).collect())
441 }
442 _ => Word(s.into()),
443 },
444 }
445 }
446}
447
448#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
450pub struct Shape(Vec<usize>);
451
452impl From<Vec<usize>> for Shape {
453 fn from(value: Vec<usize>) -> Self {
454 Shape(value)
455 }
456}
457
458impl<const N: usize> From<[usize; N]> for Shape {
459 fn from(value: [usize; N]) -> Self {
460 Shape(value.into())
461 }
462}
463
464impl Shape {
465 pub fn inner(&self) -> &[usize] {
467 &self.0
468 }
469
470 pub fn into_inner(self) -> Vec<usize> {
472 self.0
473 }
474
475 pub fn row_count(&self) -> usize {
477 self.0.len()
478 }
479
480 fn rows(&self) -> impl Iterator<Item = &usize> {
482 self.inner().iter()
483 }
484
485 pub fn fits_in(&self, cmp: &Self) -> bool {
487 if self.row_count() > cmp.row_count() {
488 false
489 } else {
490 self.rows().zip(cmp.rows()).all(|(s, c)| s <= c)
491 }
492 }
493}
494
495#[allow(missing_docs)]
498#[derive(Debug, Clone, PartialEq, Eq, Hash)]
499pub enum FormFactor {
500 Ansi,
501 Iso,
502 Ortho,
503 Colstag,
504 Custom(String),
505}
506
507impl FormFactor {
508 pub fn shape(&self) -> Shape {
510 self.fingering(&NamedFingering::Traditional)
511 .unwrap()
512 .shape()
513 }
514
515 pub fn fingering(&self, named_fingering: &NamedFingering) -> Result<Fingering> {
519 use Finger::*;
520 use FormFactor::*;
521 use NamedFingering::*;
522
523 let fingering = match (self, &named_fingering) {
524 (Ansi, Traditional) => vec![
525 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
526 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
527 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
528 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP],
529 vec![LP, LP, LT, LT, RT, RT, RP, RP],
530 ]
531 .into(),
532 (Ansi, Angle) => vec![
533 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
534 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
535 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
536 vec![LP, LR, LM, LI, LI, LI, RI, RI, RM, RR, RP, RP],
537 vec![LP, LP, LT, LT, RT, RT, RP, RP],
538 ]
539 .into(),
540 (Iso, Traditional) => vec![
541 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
542 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
543 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
544 vec![LP, LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP],
545 vec![LP, LP, LT, LT, RT, RT, RP, RP],
546 ]
547 .into(),
548 (Iso, Angle) => vec![
549 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
550 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP, RP],
551 vec![LP, LP, LR, LM, LI, LI, RI, RI, RM, RR, RP, RP, RP],
552 vec![LP, LP, LR, LM, LI, LI, LI, RI, RI, RM, RR, RP, RP],
553 vec![LP, LP, LT, LT, RT, RT, RP, RP],
554 ]
555 .into(),
556 (Ortho, Traditional) => vec![
557 vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
558 vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
559 vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
560 vec![LT, LT, LT, RT, RT, RT],
561 ]
562 .into(),
563 (Colstag, Traditional) => vec![
564 vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
565 vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
566 vec![LP, LR, LM, LI, LI, RI, RI, RM, RR, RP],
567 vec![LT, LT, LT, RT, RT, RT],
568 ]
569 .into(),
570 (board, &f) => {
571 return Err(DofErrorInner::UnsupportedKeyboardFingeringCombo(
572 board.clone(),
573 f.clone(),
574 )
575 .into());
576 }
577 };
578
579 Ok(fingering)
580 }
581
582 pub const fn is_custom(&self) -> bool {
584 matches!(self, Self::Custom(_))
585 }
586
587 pub const fn anchor(&self) -> Anchor {
591 use FormFactor::*;
592
593 match self {
594 Ansi => Anchor::new(1, 1),
595 Iso => Anchor::new(1, 1),
596 Ortho => Anchor::new(0, 0),
597 Colstag => Anchor::new(0, 0),
598 Custom(_) => Anchor::new(0, 0),
599 }
600 }
601}
602
603impl Display for FormFactor {
604 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
605 use FormFactor::*;
606
607 let s = match self {
608 Ansi => "ansi",
609 Iso => "iso",
610 Ortho => "ortho",
611 Colstag => "colstag",
612 Custom(name) => name.as_str(),
613 };
614
615 write!(f, "{s}")
616 }
617}
618
619impl FromStr for FormFactor {
620 type Err = std::convert::Infallible;
621
622 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
623 use FormFactor::*;
624
625 match s.to_lowercase().as_str() {
626 "ansi" => Ok(Ansi),
627 "iso" => Ok(Iso),
628 "ortho" => Ok(Ortho),
629 "colstag" => Ok(Colstag),
630 name => Ok(Custom(name.into())),
631 }
632 }
633}