1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! This crate contains the key and legend types used for describing layouts used internally by
//! [keyset]. It also contains utility functions for loading KLE layouts
//!
//! [keyset]: https://crates.io/crates/keyset

#![warn(
    missing_docs,
    clippy::all,
    clippy::correctness,
    clippy::suspicious,
    clippy::style,
    clippy::complexity,
    clippy::perf,
    clippy::pedantic,
    clippy::cargo,
    clippy::nursery
)]

mod legend;

#[cfg(feature = "kle")]
pub mod kle;

pub use legend::{Legend, Legends};

use geom::{Point, Rect, Size};

use color::Color;

/// The type of homing used on a homing key
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Homing {
    /// A scooped homing key, also known as a dished homing key
    Scoop,
    /// A key with a homing bar, sometimes called a line
    Bar,
    /// A key with a homing bump, also known as a nub, dot, or nipple
    Bump,
}

/// The shape of a key
#[derive(Debug, Clone, Copy)]
pub enum Shape {
    /// Not a *key* per se, but only a legend. This is usually used for labels and is the same as a
    /// decal in KLE
    None(Size),
    /// A regular key of the given size
    Normal(Size),
    /// A spacebar of the given size
    Space(Size),
    /// A homing key with the given homing type. If the homing type is [`None`] the profile's
    /// default homing type is assumed to be used
    Homing(Option<Homing>),
    /// A stepped caps lock key, i.e. a 1.25u key with additional 0.5u step on the right
    SteppedCaps,
    /// A vertically-aligned ISO enter, i.e. an ISO enter where legends are aligned within the
    /// vertical 1.25u &times; 2.0u section of the key
    IsoVertical,
    /// A horizontally-aligned ISO enter, i.e. an ISO enter where legends are aligned within the
    /// horizontal 1.5u top section of the key
    IsoHorizontal,
}

impl Shape {
    /// The outer bounding rectangle of the key shape, i.e. the bounding box of the key shape. The
    /// inner and outer bounds are the same for regular-shaped keys, but are different for stepped
    /// keys, L-shaped keys, etc.
    #[must_use]
    pub fn outer_rect(self) -> Rect {
        match self {
            Self::None(size) | Self::Normal(size) | Self::Space(size) => {
                Rect::from_origin_size(Point::ORIGIN, size)
            }
            Self::Homing(..) => Rect::from_origin_size(Point::ORIGIN, (1.0, 1.0)),
            Self::SteppedCaps => Rect::from_origin_size(Point::ORIGIN, (1.75, 1.0)),
            Self::IsoVertical | Self::IsoHorizontal => {
                Rect::from_origin_size(Point::ORIGIN, (1.5, 2.0))
            }
        }
    }

    /// The inner bounding rectangle of the key shape, i.e. the bounds for the part of the key
    /// containing the legend. The inner and outer bounds are the same for regular-shaped keys, but
    /// are different for stepped keys, L-shaped keys, etc.
    #[must_use]
    pub fn inner_rect(self) -> Rect {
        match self {
            Self::None(size) | Self::Normal(size) | Self::Space(size) => {
                Rect::from_origin_size(Point::ORIGIN, size)
            }
            Self::Homing(..) => Rect::from_origin_size(Point::ORIGIN, (1.0, 1.0)),
            Self::SteppedCaps => Rect::from_origin_size(Point::ORIGIN, (1.25, 1.0)),
            Self::IsoVertical => Rect::from_origin_size((0.25, 0.0), (1.25, 2.0)),
            Self::IsoHorizontal => Rect::from_origin_size(Point::ORIGIN, (1.5, 1.0)),
        }
    }
}

/// A key
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Key {
    /// The position of the key
    pub position: Point,
    /// The key's shape
    pub shape: Shape,
    /// The key's colour
    pub color: Color,
    /// The key's legends
    pub legends: Legends,
}

impl Key {
    /// A new blank key
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// An example non-blank key
    #[must_use]
    pub fn example() -> Self {
        Self {
            legends: Legends::example(),
            ..Self::default()
        }
    }
}

impl Default for Key {
    fn default() -> Self {
        Self {
            position: Point::ORIGIN,
            shape: Shape::Normal(Size::new(1., 1.)),
            color: Color::new(0.8, 0.8, 0.8),
            legends: Legends::default(),
        }
    }
}

#[cfg(test)]
pub mod tests {
    use assert_matches::assert_matches;

    use super::*;

    #[test]
    fn shape_outer_size() {
        assert_eq!(
            Shape::Normal(Size::new(2.25, 1.)).outer_rect(),
            Rect::new(0.0, 0.0, 2.25, 1.)
        );
        assert_eq!(
            Shape::IsoVertical.outer_rect(),
            Rect::new(0.0, 0.0, 1.5, 2.0)
        );
        assert_eq!(
            Shape::IsoHorizontal.outer_rect(),
            Rect::new(0.0, 0.0, 1.5, 2.0)
        );
        assert_eq!(
            Shape::SteppedCaps.outer_rect(),
            Rect::new(0.0, 0.0, 1.75, 1.0)
        );
    }

    #[test]
    fn shape_inner_size() {
        assert_eq!(
            Shape::Normal(Size::new(2.25, 1.)).inner_rect(),
            Rect::new(0.0, 0.0, 2.25, 1.)
        );
        assert_eq!(
            Shape::IsoVertical.inner_rect(),
            Rect::new(0.25, 0.0, 1.5, 2.0)
        );
        assert_eq!(
            Shape::IsoHorizontal.inner_rect(),
            Rect::new(0.0, 0.0, 1.5, 1.0)
        );
        assert_eq!(
            Shape::SteppedCaps.inner_rect(),
            Rect::new(0.0, 0.0, 1.25, 1.0)
        );
    }

    #[test]
    fn key_new() {
        let key = Key::new();

        assert_eq!(key.position, Point::new(0., 0.));
        assert_matches!(key.shape, Shape::Normal(size) if size == Size::new(1., 1.));
        assert_eq!(key.color, Color::new(0.8, 0.8, 0.8));
        for legend in key.legends {
            assert!(legend.is_none());
        }
    }

    #[test]
    fn key_example() {
        let key = Key::example();
        let legend_is_some = [true, false, true, false, false, false, true, false, true];

        assert_eq!(key.position, Point::new(0., 0.));
        assert_matches!(key.shape, Shape::Normal(size) if size == Size::new(1., 1.));
        assert_eq!(key.color, Color::new(0.8, 0.8, 0.8));
        for (legend, is_some) in key.legends.into_iter().zip(legend_is_some) {
            assert_eq!(legend.is_some(), is_some);
        }
    }
}