Skip to main content

libdof/
keyboard.rs

1//! Contains the `Keyboard` struct and helpers which can be used to describe physical keyboards.
2
3use std::{cmp::Ordering, str::FromStr};
4
5use serde::{Deserialize, Serialize};
6use serde_with::{DisplayFromStr, serde_as};
7
8use crate::{
9    Anchor, DofError, DofErrorInner as DE, Fingering, FormFactor, Keyboard, NamedFingering, Result,
10    keyboard_conv,
11};
12
13/// Representation of a physical key on a keyboard, where `(x, y)` are the top left and the width and
14/// height go right and down respectively.
15#[derive(Clone, Debug, PartialEq)]
16pub struct PhysicalKey {
17    x: f64,
18    y: f64,
19    width: f64,
20    height: f64,
21}
22
23impl PhysicalKey {
24    /// Get the `x` coordinate.
25    pub const fn x(&self) -> f64 {
26        self.x
27    }
28
29    /// Get the `y` coordinate.
30    pub const fn y(&self) -> f64 {
31        self.y
32    }
33
34    /// Get the width of the key, which is the space from `x` to the right side of the key.
35    pub const fn width(&self) -> f64 {
36        self.width
37    }
38
39    /// Get the height of the key, which is the space from `y` to the bottom of the key.
40    pub const fn height(&self) -> f64 {
41        self.height
42    }
43
44    /// Create a new key with x, y coordinates where the width and height are set to 1.0.
45    pub const fn xy(x: f64, y: f64) -> Self {
46        Self {
47            x,
48            y,
49            width: 1.0,
50            height: 1.0,
51        }
52    }
53
54    /// Create a new key with x, y coordinates and a width, with a set height of 1.0.
55    pub const fn xyw(x: f64, y: f64, width: f64) -> Self {
56        Self {
57            x,
58            y,
59            width,
60            height: 1.0,
61        }
62    }
63
64    /// Create a new key with x, y coordinates, a width and a height.
65    pub const fn xywh(x: f64, y: f64, width: f64, height: f64) -> Self {
66        Self {
67            x,
68            y,
69            width,
70            height,
71        }
72    }
73
74    /// If width or height are negative, moves `x` and `y` coordinates around accordingly
75    pub(crate) fn normalized(self) -> Self {
76        let (x, width) = match self.width < 0.0 {
77            true => (self.x + self.width, self.width.abs()),
78            false => (self.x, self.width),
79        };
80
81        let (y, height) = match self.height < 0.0 {
82            true => (self.y + self.height, self.height.abs()),
83            false => (self.y, self.height),
84        };
85
86        Self {
87            x,
88            y,
89            width,
90            height,
91        }
92    }
93}
94
95impl std::fmt::Display for PhysicalKey {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        let omit = |v: f64| v >= 0.999999 || v <= 1.000001;
98
99        match (omit(self.width), omit(self.height)) {
100            (true, true) => write!(f, "{} {}", self.x, self.y),
101            (false, true) => write!(f, "{} {} {}", self.x, self.y, self.width),
102            (_, false) => write!(f, "{} {} {} {}", self.x, self.y, self.width, self.height),
103        }
104    }
105}
106
107impl FromStr for PhysicalKey {
108    type Err = DofError;
109
110    fn from_str(s: &str) -> Result<Self> {
111        let trimmed = s.trim();
112
113        if trimmed.is_empty() {
114            return Err(DE::EmptyPhysKey.into());
115        }
116
117        let vals = trimmed
118            .split_whitespace()
119            .map(|s| s.parse::<f64>())
120            .collect::<std::result::Result<Vec<_>, _>>()
121            .map_err(|_| DE::KeyParseError(trimmed.into()))?;
122
123        match vals.as_slice() {
124            &[x, y] => Ok(Self::xy(x, y)),
125            &[x, y, width] => Ok(Self::xyw(x, y, width)),
126            &[x, y, width, height] => Ok(Self::xywh(x, y, width, height)),
127            sl => Err(DE::ValueAmountError(sl.len(), trimmed.into()).into()),
128        }
129    }
130}
131
132/// Representation of a physical keyboard, based on a configuration of physical keys.
133#[serde_as]
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
135pub struct PhysicalKeyboard(#[serde_as(as = "Vec<Vec<DisplayFromStr>>")] Vec<Vec<PhysicalKey>>);
136
137impl Keyboard for PhysicalKeyboard {
138    type K = PhysicalKey;
139
140    fn inner(&self) -> &[Vec<Self::K>] {
141        &self.0
142    }
143
144    fn into_inner(self) -> Vec<Vec<Self::K>> {
145        self.0
146    }
147}
148
149impl From<Vec<Vec<PhysicalKey>>> for PhysicalKeyboard {
150    fn from(v: Vec<Vec<PhysicalKey>>) -> Self {
151        Self(v)
152    }
153}
154
155/// Key representation with a width only. Useful for boards that have no vertical stagger where
156/// each key is next to each other.
157#[derive(Clone, Debug, PartialEq)]
158pub struct RelativeKey {
159    pub(crate) width: f64,
160    pub(crate) has_key: bool,
161}
162
163impl std::fmt::Display for RelativeKey {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self.width {
166            1.0 => write!(f, "k"),
167            w if w.fract() == 0.0 => write!(f, "{}k", w as u64),
168            w => write!(f, "{w}k"),
169        }
170    }
171}
172
173impl FromStr for RelativeKey {
174    type Err = DofError;
175
176    fn from_str(s: &str) -> Result<Self> {
177        match s.strip_suffix('k') {
178            Some(s) if !s.is_empty() => Ok(Self {
179                width: s.parse::<f64>()?,
180                has_key: true,
181            }),
182            Some(_) => Ok(Self {
183                width: 1.0,
184                has_key: true,
185            }),
186            None => Ok(Self {
187                width: s.parse::<f64>()?,
188                has_key: false,
189            }),
190        }
191    }
192}
193
194/// Representation of a physical keyboard where each row is built of
195/// [`RelativeKey`](crate::keyboard::RelativeKey)s as a shorthand for defining each key individually.
196#[serde_as]
197#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
198pub struct RelativeKeyboard(#[serde_as(as = "Vec<RelativeKeyboardRow>")] Vec<Vec<RelativeKey>>);
199
200impl Keyboard for RelativeKeyboard {
201    type K = RelativeKey;
202
203    fn inner(&self) -> &[Vec<Self::K>] {
204        &self.0
205    }
206
207    fn into_inner(self) -> Vec<Vec<Self::K>> {
208        self.0
209    }
210}
211
212impl From<Vec<Vec<RelativeKey>>> for RelativeKeyboard {
213    fn from(v: Vec<Vec<RelativeKey>>) -> Self {
214        Self(v)
215    }
216}
217
218keyboard_conv!(RelativeKey, RelativeKeyboardRow);
219
220/// Representation of a physical keyboard using a form factor and an optional anchor. If these are
221/// known defaults, it can be converted to a physical keyboard directly.
222#[serde_as]
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
224pub struct NamedKeyboard {
225    #[serde_as(as = "DisplayFromStr")]
226    #[serde(rename = "type")]
227    pub(crate) form_factor: FormFactor,
228    pub(crate) anchor: Option<Anchor>,
229}
230
231impl TryFrom<FormFactor> for PhysicalKeyboard {
232    type Error = DofError;
233
234    fn try_from(form_factor: FormFactor) -> Result<Self> {
235        let kb = match form_factor {
236            FormFactor::Ansi => vec![
237                phys_row(&[(1.0, 1), (1.0, 12), (2.0, 1)], 0.0, 0.0),
238                phys_row(&[(1.5, 1), (1.0, 12), (1.5, 1)], 0.0, 1.0),
239                phys_row(&[(1.75, 1), (1.0, 11), (2.25, 1)], 0.0, 2.0),
240                phys_row(&[(2.25, 1), (1.0, 10), (2.75, 1)], 0.0, 3.0),
241                phys_row(&[(1.25, 3), (6.25, 1), (1.25, 4)], 0.0, 4.0),
242            ],
243            FormFactor::Iso => {
244                let mut iso = vec![
245                    phys_row(&[(1.0, 1), (1.0, 12), (2.0, 1)], 0.0, 0.0),
246                    phys_row(&[(1.5, 1), (1.0, 12) /* iso */], 0.0, 1.0),
247                    phys_row(&[(1.75, 1), (1.0, 12) /*enter*/], 0.0, 2.0),
248                    phys_row(&[(1.25, 1), (1.0, 11), (2.75, 1)], 0.0, 3.0),
249                    phys_row(&[(1.25, 3), (6.25, 1), (1.25, 4)], 0.0, 4.0),
250                ];
251
252                // ISO enter. This is an approximation because in reality it is not actually a rectangle.
253                iso[1].push(PhysicalKey::xywh(13.75, 2.0, 1.5, 2.0));
254
255                iso
256            }
257            FormFactor::Ortho => vec![
258                phys_row(&[(1.0, 10)], 0.0, 0.0),
259                phys_row(&[(1.0, 10)], 0.0, 1.0),
260                phys_row(&[(1.0, 10)], 0.0, 2.0),
261                phys_row(&[(1.0, 6)], 2.0, 3.0),
262            ],
263            FormFactor::Colstag => vec![
264                vec![
265                    PhysicalKey::xy(0.0, 0.45),
266                    PhysicalKey::xy(1.0, 0.15),
267                    PhysicalKey::xy(2.0, 0.0),
268                    PhysicalKey::xy(3.0, 0.15),
269                    PhysicalKey::xy(4.0, 0.30),
270                    PhysicalKey::xy(7.0, 0.30),
271                    PhysicalKey::xy(8.0, 0.15),
272                    PhysicalKey::xy(9.0, 0.0),
273                    PhysicalKey::xy(10.0, 0.15),
274                    PhysicalKey::xy(11.0, 0.45),
275                ],
276                vec![
277                    PhysicalKey::xy(0.0, 1.45),
278                    PhysicalKey::xy(1.0, 1.15),
279                    PhysicalKey::xy(2.0, 1.0),
280                    PhysicalKey::xy(3.0, 1.15),
281                    PhysicalKey::xy(4.0, 1.30),
282                    PhysicalKey::xy(7.0, 1.30),
283                    PhysicalKey::xy(8.0, 1.15),
284                    PhysicalKey::xy(9.0, 1.0),
285                    PhysicalKey::xy(10.0, 1.15),
286                    PhysicalKey::xy(11.0, 1.45),
287                ],
288                vec![
289                    PhysicalKey::xy(0.0, 2.45),
290                    PhysicalKey::xy(1.0, 2.15),
291                    PhysicalKey::xy(2.0, 2.0),
292                    PhysicalKey::xy(3.0, 2.15),
293                    PhysicalKey::xy(4.0, 2.30),
294                    PhysicalKey::xy(7.0, 2.30),
295                    PhysicalKey::xy(8.0, 2.15),
296                    PhysicalKey::xy(9.0, 2.0),
297                    PhysicalKey::xy(10.0, 2.15),
298                    PhysicalKey::xy(11.0, 2.45),
299                ],
300                vec![
301                    PhysicalKey::xy(2.4, 3.3),
302                    PhysicalKey::xy(3.5, 3.5),
303                    PhysicalKey::xy(4.7, 3.8),
304                    PhysicalKey::xy(6.3, 3.8),
305                    PhysicalKey::xy(7.5, 3.5),
306                    PhysicalKey::xy(8.6, 3.3),
307                ],
308            ],
309            c @ FormFactor::Custom(_) => return Err(DE::UnknownKeyboardType(c).into()),
310        };
311
312        Ok(kb.into())
313    }
314}
315
316impl From<RelativeKeyboard> for PhysicalKeyboard {
317    fn from(rkb: RelativeKeyboard) -> Self {
318        rkb.into_inner()
319            .into_iter()
320            .enumerate()
321            .map(|(y, r)| {
322                let mut x = 0.0;
323
324                r.into_iter()
325                    .filter_map(|rk| {
326                        let r = PhysicalKey::xyw(x, y as f64, rk.width);
327                        x += rk.width;
328                        rk.has_key.then_some(r)
329                    })
330                    .collect()
331            })
332            .collect::<Vec<_>>()
333            .into()
334    }
335}
336
337impl From<PhysicalKeyboard> for ParseKeyboard {
338    fn from(board: PhysicalKeyboard) -> Self {
339        if board.inner().is_empty() {
340            return Self::Full(Default::default());
341        }
342
343        let relative_board = board
344            .rows()
345            .enumerate()
346            .map(|(row_i, r)| match r.split_first() {
347                None => Some(vec![]),
348                Some((f, r)) if f.y == row_i as f64 => {
349                    let mut res = match f.x == 0.0 {
350                        true => vec![RelativeKey {
351                            width: f.width,
352                            has_key: true,
353                        }],
354                        _ => vec![
355                            RelativeKey {
356                                width: f.x,
357                                has_key: false,
358                            },
359                            RelativeKey {
360                                width: f.width,
361                                has_key: true,
362                            },
363                        ],
364                    };
365
366                    let mut last_x = f.width + f.x;
367
368                    for key in r {
369                        if key.y != row_i as f64 {
370                            return None;
371                        }
372                        match key.x.total_cmp(&last_x) {
373                            Ordering::Less => return None,
374                            Ordering::Equal => res.push(RelativeKey {
375                                width: key.width,
376                                has_key: true,
377                            }),
378                            Ordering::Greater => {
379                                res.push(RelativeKey {
380                                    width: key.x - last_x,
381                                    has_key: false,
382                                });
383                                res.push(RelativeKey {
384                                    width: key.width,
385                                    has_key: true,
386                                });
387                            }
388                        }
389
390                        last_x = key.x + key.width;
391                    }
392
393                    Some(res)
394                }
395                _ => None,
396            })
397            .collect::<Option<Vec<Vec<RelativeKey>>>>();
398
399        match relative_board {
400            Some(b) => Self::Relative(b.into()),
401            None => Self::Full(board),
402        }
403    }
404}
405
406/// Abstraction over the way an actual .dof file is allowed to represent a physical keyboard for a
407/// specific layout. Has three different ways of doing so.
408/// * `Named`: a [`KeyboardType`](crate::KeyboardType) name. If a custom name is provided,
409/// the `Dof` can likely not be parsed.
410/// * `Relative`: a [`RelativeKeyboard`](crate::keyboard::RelativeKeyboard),
411/// * `Full`: a [`PhysicalKeyboard`](crate::keyboard::PhysicalKeyboard), which is what is converted
412/// to when converting to `Dof`.
413#[serde_as]
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415#[serde(untagged)]
416pub enum ParseKeyboard {
417    /// * `Named`: a [`KeyboardType`](crate::KeyboardType) name. If a custom name is provided,
418    /// the `Dof` can likely not be parsed.
419    Named(#[serde_as(as = "DisplayFromStr")] FormFactor),
420    // NamedAnchor(NamedKeyboard),
421    /// * `Relative`: a [`RelativeKeyboard`](crate::keyboard::RelativeKeyboard),
422    Relative(RelativeKeyboard),
423    /// * `Full`: a [`PhysicalKeyboard`](crate::keyboard::PhysicalKeyboard), which is what is converted
424    /// to when converting to `Dof`.
425    Full(PhysicalKeyboard),
426}
427
428impl ParseKeyboard {
429    /// Get the default anchor for a parsed kebyoard. This is (0, 0) for anything custom, (1, 1) for
430    /// `Ansi` and `Iso` boards (as the vast majority of keyboard layouts doesn't remap the number
431    /// row or special keys on the left) and (0, 0) for `Ortho` and `Colstag`.
432    pub const fn anchor(&self) -> Anchor {
433        match self {
434            ParseKeyboard::Named(n) => n.anchor(),
435            _ => Anchor(0, 0),
436        }
437    }
438
439    /// Given a known fingering from `NamedFingering`, provide a `Fingering` object with all keys on a board
440    /// like that specified. Will return an error if any combination is provided that isn't valid. This
441    /// is the case for any custom physical boards, but also for `KeyboardType::Ortho` and
442    /// `NamedFingering::Angle` for example.
443    pub fn fingering(&self, named_fingering: &NamedFingering) -> Result<Fingering> {
444        match self {
445            ParseKeyboard::Named(n) => n.fingering(named_fingering),
446            _ => Err(DE::FingeringForCustomKeyboard.into()),
447        }
448    }
449}
450
451impl TryFrom<ParseKeyboard> for PhysicalKeyboard {
452    type Error = DofError;
453
454    fn try_from(value: ParseKeyboard) -> Result<Self> {
455        match value {
456            ParseKeyboard::Named(board) => board.try_into(),
457            ParseKeyboard::Relative(r) => Ok(r.into()),
458            ParseKeyboard::Full(f) => Ok(f),
459        }
460    }
461}
462
463pub(crate) fn phys_row(widths: &[(f64, usize)], x_offset: f64, y_offset: f64) -> Vec<PhysicalKey> {
464    let mut x = x_offset;
465
466    widths
467        .iter()
468        .copied()
469        .flat_map(|(width, count)| std::iter::repeat_n(width, count))
470        .map(|w| {
471            let pk = PhysicalKey::xyw(x, y_offset, w);
472            x += w;
473            pk
474        })
475        .collect()
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481    use assert_matches::assert_matches;
482
483    #[test]
484    fn parse_physical_key() {
485        let k1 = "0.0 0.0".parse::<PhysicalKey>();
486        let k2 = "1 2 3".parse::<PhysicalKey>();
487        let k3 = "0 0 4 2".parse::<PhysicalKey>();
488        let k4 = "0.1".parse::<PhysicalKey>();
489        let k5 = "0.1 0.2".parse::<PhysicalKey>();
490        let k6 = "".parse::<PhysicalKey>();
491
492        assert_eq!(k1, Ok(PhysicalKey::xy(0.0, 0.0)));
493        assert_eq!(k2, Ok(PhysicalKey::xywh(1.0, 2.0, 3.0, 1.0)));
494        assert_eq!(k3, Ok(PhysicalKey::xywh(0.0, 0.0, 4.0, 2.0)));
495        assert_eq!(
496            k4,
497            Err(DofError::from(DE::ValueAmountError(1, "0.1".into())))
498        );
499        assert_eq!(k5, Ok(PhysicalKey::xy(0.1, 0.2)));
500        assert_eq!(k6, Err(DofError::from(DE::EmptyPhysKey)));
501    }
502
503    #[test]
504    fn parse_physical_key_board() {
505        let board_str = r#"[
506                [
507                    "1.8125 0.5 2 3"
508                ]
509            ]
510        "#;
511
512        let kb = serde_json::from_str::<PhysicalKeyboard>(board_str)
513            .expect("parsing of Keyboard failed");
514
515        let cmp = PhysicalKeyboard(vec![vec![PhysicalKey {
516            x: 1.8125,
517            y: 0.5,
518            width: 2.0,
519            height: 3.0,
520        }]]);
521
522        assert_eq!(kb, cmp);
523
524        let parsed = ParseKeyboard::from(kb);
525
526        assert_matches!(parsed, ParseKeyboard::Full(_))
527    }
528
529    #[test]
530    fn parse_relative_key() {
531        let k1 = "k".parse::<RelativeKey>();
532        let k2 = "2.3k".parse::<RelativeKey>();
533        let k3 = "5".parse::<RelativeKey>();
534        let k4 = "".parse::<RelativeKey>();
535
536        assert_eq!(
537            k1,
538            Ok(RelativeKey {
539                width: 1.0,
540                has_key: true
541            })
542        );
543        assert_eq!(
544            k2,
545            Ok(RelativeKey {
546                width: 2.3,
547                has_key: true
548            })
549        );
550        assert_eq!(
551            k3,
552            Ok(RelativeKey {
553                width: 5.0,
554                has_key: false
555            })
556        );
557
558        match k4 {
559            Ok(_) => panic!("Should be a `ParseFloatError`, actually: '{k4:?}'"),
560            Err(e) => assert!(matches!(e.0.as_ref(), DE::ParseFloatError(_))),
561        }
562    }
563
564    #[test]
565    fn row_defined_keyboard() {
566        let board_str = r#"
567            [
568                "k 3.2k   2 8k"
569            ]
570        "#;
571
572        let kb = serde_json::from_str::<ParseKeyboard>(board_str)
573            .expect("parsing of RelativeKeyboard failed");
574
575        let cmp = ParseKeyboard::Relative(RelativeKeyboard(vec![vec![
576            RelativeKey {
577                width: 1.0,
578                has_key: true,
579            },
580            RelativeKey {
581                width: 3.2,
582                has_key: true,
583            },
584            RelativeKey {
585                width: 2.0,
586                has_key: false,
587            },
588            RelativeKey {
589                width: 8.0,
590                has_key: true,
591            },
592        ]]));
593
594        assert_eq!(kb, cmp);
595
596        let keyboard = PhysicalKeyboard::try_from(cmp)
597            .expect("couldn't convert to physical keyboard from parse keyboard: ");
598
599        let parsed = ParseKeyboard::from(keyboard);
600
601        println!("{parsed:?}");
602
603        assert_matches!(parsed, ParseKeyboard::Relative(_))
604    }
605
606    #[test]
607    fn named_parsed_keyboard() {
608        let board_str = "\"ansi\"";
609
610        let board = serde_json::from_str::<ParseKeyboard>(board_str)
611            .expect("parsing of ParseKeyboard failed");
612
613        assert_eq!(board, ParseKeyboard::Named(FormFactor::Ansi));
614
615        let kb = PhysicalKeyboard::try_from(board)
616            .expect("error encountered while converting to physical keyboard: ")
617            .resized(FormFactor::Ansi.anchor(), vec![10, 11, 10].into());
618
619        let cmp = &PhysicalKey {
620            x: 11.25,
621            y: 3.0,
622            width: 1.0,
623            height: 1.0,
624        };
625
626        match kb {
627            Ok(kb) => match kb.last() {
628                Some(r) => match r.last() {
629                    Some(k) => assert_eq!(k, cmp),
630                    None => panic!("Row has no keys"),
631                },
632                None => panic!("Keyboard is empty"),
633            },
634            Err(e) => panic!("{:?}", e),
635        }
636    }
637
638    #[test]
639    fn rows_parsed_keyboard() {
640        let board_str = r#"[
641            "1 k k k  2  k k k 1",
642            "k k k k  2  k k k k",
643            "  3   k k k k   3  "
644        ]"#;
645
646        let board: PhysicalKeyboard = serde_json::from_str::<ParseKeyboard>(board_str)
647            .expect("parsing of ParseKeyboard failed")
648            .try_into()
649            .expect("error encountered while converting to physical keyboard: ");
650
651        assert_eq!(board.inner()[0].len(), 6);
652        assert_eq!(board.inner()[2].len(), 4);
653        assert_eq!(board.inner()[0][3].x, 6.0);
654    }
655}