keyset_key/kle/
mod.rs

1//! Load KLE layouts from JSON files
2
3mod error;
4
5use geom::{Point, Size};
6use kle_serial as kle;
7
8use crate::{Homing, Key, Legend, Shape};
9pub use error::{Error, Result};
10
11fn shape_from_kle(key: &kle::Key) -> Result<Shape> {
12    const STEP_CAPS: [f64; 6] = [1.25, 1.0, 0.0, 0.0, 1.75, 1.0];
13    const ISO_VERT: [f64; 6] = [1.25, 2.0, -0.25, 0.0, 1.5, 1.0];
14    const ISO_HORIZ: [f64; 6] = [1.5, 1.0, 0.25, 0.0, 1.25, 2.0];
15
16    fn is_close<const N: usize>(a: &[f64; N], b: &[f64; N]) -> bool {
17        a.iter().zip(b).all(|(a, b)| (b - a).abs() < 1e-2)
18    }
19
20    let &kle::Key {
21        width: w,
22        height: h,
23        x2,
24        y2,
25        width2: w2,
26        height2: h2,
27        ..
28    } = key;
29
30    let is_normal = is_close(&[x2, y2, w2, h2], &[0.0, 0.0, w, h]);
31    let is_1u = is_normal && is_close(&[w, h], &[1.0, 1.0]);
32
33    let dims = [w, h, x2, y2, w2, h2];
34
35    if is_1u && (key.profile.contains("scoop") || key.profile.contains("dish")) {
36        Ok(Shape::Homing(Some(Homing::Scoop)))
37    } else if is_1u && key.profile.contains("bar") {
38        Ok(Shape::Homing(Some(Homing::Bar)))
39    } else if is_1u && (key.profile.contains("bump") || key.profile.contains("dot")) {
40        Ok(Shape::Homing(Some(Homing::Bump)))
41    } else if is_normal && key.profile.contains("space") {
42        Ok(Shape::Space(Size::new(w, h)))
43    } else if is_1u && key.homing {
44        Ok(Shape::Homing(None))
45    } else if key.decal {
46        Ok(Shape::None(Size::new(w, h)))
47    } else if is_normal {
48        Ok(Shape::Normal(Size::new(w, h)))
49    } else if is_close(&dims, &STEP_CAPS) {
50        Ok(Shape::SteppedCaps)
51    } else if is_close(&dims, &ISO_VERT) {
52        Ok(Shape::IsoVertical)
53    } else if is_close(&dims, &ISO_HORIZ) {
54        Ok(Shape::IsoHorizontal)
55    } else {
56        // TODO support all key shapes/sizes
57        Err(Error::UnsupportedKeySize {
58            w,
59            h,
60            x2,
61            y2,
62            w2,
63            h2,
64        })
65    }
66}
67
68impl From<kle::Legend> for Legend {
69    fn from(legend: kle::Legend) -> Self {
70        let kle::Legend { text, size, color } = legend;
71        Self {
72            text,
73            size_idx: size,
74            color: color.rgb().into(),
75        }
76    }
77}
78
79impl TryFrom<kle::Key> for Key {
80    type Error = Error;
81
82    fn try_from(mut key: kle::Key) -> Result<Self> {
83        let position = Point::new(key.x + key.x2.min(0.), key.y + key.y2.min(0.));
84        let shape = shape_from_kle(&key)?;
85        let color = key.color.rgb().into();
86        let legends = {
87            let mut arr = <[Option<kle::Legend>; 9]>::default();
88            arr.swap_with_slice(&mut key.legends[..9]);
89            arr
90        };
91        let legends = legends.map(|l| l.map(Legend::from)).into();
92        Ok(Self {
93            position,
94            shape,
95            color,
96            legends,
97        })
98    }
99}
100
101/// Loads a KLE layout from a JSON string into a [`Vec<Key>`]
102///
103/// # Errors
104///
105/// If an
106pub fn from_json(json: &str) -> Result<Vec<Key>> {
107    let key_iter: kle::KeyIterator = serde_json::from_str(json)?;
108    key_iter.map(Key::try_from).collect()
109}
110
111#[cfg(test)]
112mod tests {
113    use assert_matches::assert_matches;
114    use unindent::unindent;
115
116    use super::*;
117
118    #[test]
119    fn key_shape_from_kle() {
120        let default_key = shape_from_kle(&kle::Key::default()).unwrap();
121        let decal = shape_from_kle(&kle::Key {
122            decal: true,
123            ..Default::default()
124        })
125        .unwrap();
126        let space = shape_from_kle(&kle::Key {
127            profile: "space".into(),
128            ..Default::default()
129        })
130        .unwrap();
131        let homing_default = shape_from_kle(&kle::Key {
132            homing: true,
133            ..Default::default()
134        })
135        .unwrap();
136        let homing_scoop = shape_from_kle(&kle::Key {
137            profile: "scoop".into(),
138            ..Default::default()
139        })
140        .unwrap();
141        let homing_bar = shape_from_kle(&kle::Key {
142            profile: "bar".into(),
143            ..Default::default()
144        })
145        .unwrap();
146        let homing_bump = shape_from_kle(&kle::Key {
147            profile: "bump".into(),
148            ..Default::default()
149        })
150        .unwrap();
151        let regular_key = shape_from_kle(&kle::Key {
152            width: 2.25,
153            height: 1.,
154            x2: 0.,
155            y2: 0.,
156            width2: 2.25,
157            height2: 1.,
158            ..Default::default()
159        })
160        .unwrap();
161        let iso_horiz = shape_from_kle(&kle::Key {
162            width: 1.5,
163            height: 1.,
164            x2: 0.25,
165            y2: 0.,
166            width2: 1.25,
167            height2: 2.,
168            ..Default::default()
169        })
170        .unwrap();
171        let iso_vert = shape_from_kle(&kle::Key {
172            width: 1.25,
173            height: 2.,
174            x2: -0.25,
175            y2: 0.,
176            width2: 1.5,
177            height2: 1.,
178            ..Default::default()
179        })
180        .unwrap();
181        let step_caps = shape_from_kle(&kle::Key {
182            width: 1.25,
183            height: 1.,
184            x2: 0.,
185            y2: 0.,
186            width2: 1.75,
187            height2: 1.,
188            ..Default::default()
189        })
190        .unwrap();
191
192        assert_matches!(default_key, Shape::Normal(size) if size == Size::new(1.0, 1.0));
193        assert_matches!(regular_key, Shape::Normal(size) if size == Size::new(2.25, 1.0));
194        assert_matches!(decal, Shape::None(size) if size == Size::new(1.0, 1.0));
195        assert_matches!(space, Shape::Space(size) if size == Size::new(1.0, 1.0));
196        assert_matches!(homing_default, Shape::Homing(None));
197        assert_matches!(homing_scoop, Shape::Homing(Some(Homing::Scoop)));
198        assert_matches!(homing_bar, Shape::Homing(Some(Homing::Bar)));
199        assert_matches!(homing_bump, Shape::Homing(Some(Homing::Bump)));
200        assert_matches!(iso_horiz, Shape::IsoHorizontal);
201        assert_matches!(iso_vert, Shape::IsoVertical);
202        assert_matches!(step_caps, Shape::SteppedCaps);
203    }
204
205    #[test]
206    fn key_shape_from_kle_invalid() {
207        let invalid = shape_from_kle(&kle::Key {
208            width: 1.,
209            height: 1.,
210            x2: -0.25,
211            y2: 0.,
212            width2: 1.5,
213            height2: 1.,
214            ..Default::default()
215        });
216
217        assert!(invalid.is_err());
218        assert_eq!(
219            format!("{}", invalid.unwrap_err()),
220            format!(concat!(
221                "unsupported non-standard key size (w: 1.00, h: 1.00, ",
222                "x2: -0.25, y2: 0.00, w2: 1.50, h2: 1.00). Note only ISO enter and stepped caps ",
223                "are supported as special cases"
224            ))
225        );
226    }
227
228    #[test]
229    fn kle_from_json() {
230        let result1: Vec<_> = from_json(&unindent(
231            r#"[
232                {
233                    "meta": "data"
234                },
235                [
236                    {
237                        "a": 4,
238                        "unknown": "key"
239                    },
240                    "A",
241                    "B",
242                    {
243                        "x": -0.5,
244                        "y": 0.25
245                    },
246                    "C"
247                ],
248                [
249                    "D"
250                ]
251            ]"#,
252        ))
253        .unwrap();
254
255        assert_eq!(result1.len(), 4);
256        assert_eq!(result1[0].position.x, 0.0);
257        assert_eq!(result1[1].position.x, 1.0);
258        assert_eq!(result1[2].position.x, 1.5);
259        assert_eq!(result1[3].position.x, 0.0);
260
261        let result2: Vec<_> = from_json(&unindent(
262            r#"[
263                [
264                    "A"
265                ]
266            ]"#,
267        ))
268        .unwrap();
269
270        assert_eq!(result2.len(), 1);
271    }
272}