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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
use crate::Pos;
use crate::PosPair;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::fs;
use std::io;
use std::mem;
use toml;

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Formats {
    pub standard: Option<Keys>,
    pub angle: Option<Keys>,
}

#[derive(Clone, Deserialize, Serialize, Debug)]
/// Wraps key data with the layout's metadata.
pub struct Layout {
    pub name: String,
    /// Name of the creator of the layout
    pub author: String,
    /// Link to the layout's web page
    pub link: Option<String>,
    /// Year that the layout was released in
    pub year: u32,
    pub formats: Formats,
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Keys {
    pub matrix: Vec<Vec<char>>,
    pub map: HashMap<char, Pos>,
    pub home_row: u8,
    pub thumb_row: Option<u8>,
}

#[derive(Debug, PartialEq)]
pub enum LayoutError {
    FileError,
    TomlError,
}

impl error::Error for LayoutError {}

impl fmt::Display for LayoutError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            LayoutError::FileError => write!(f, "Parsing error"),
            LayoutError::TomlError => write!(f, "Parsing error"),
        }
    }
}

impl From<toml::de::Error> for LayoutError {
    fn from(_: toml::de::Error) -> Self {
        LayoutError::TomlError
    }
}

impl From<io::Error> for LayoutError {
    fn from(_: io::Error) -> Self {
        LayoutError::FileError
    }
}

impl Keys {
    pub fn new(matrix: Vec<Vec<char>>, home_row: u8, thumb_row: Option<u8>) -> Self {
        let mut k = Keys {
            matrix,
            map: HashMap::new(),
            home_row,
            thumb_row,
        };
        k.fill_map();
        k
    }
    pub fn fill_map(&mut self) {
        for (y, row) in self.matrix.iter().enumerate() {
            for (x, key) in row.iter().enumerate() {
                self.map.insert(*key, Pos { col: x, row: y });
            }
        }
    }
}

impl Layout {
    /// Reads a layout file and parses it into a Layout.
    /// ```rust
    /// let l = keynergy::Layout::load("testdata/semimak_jq.toml").unwrap();
    /// assert_eq!(l.name, "Semimak JQ");
    /// ```
    pub fn load(path: &str) -> Result<Layout, LayoutError> {
        // read file
        let file = fs::read_to_string(path)?;
        let result = toml::from_str(&file);
        let mut layout: Layout = result?;
        // fills the maps for each format if they exist
        [&mut layout.formats.standard, &mut layout.formats.angle].map(|l| {
            if let Some(l) = l.as_mut() {
                l.fill_map()
            }
        });
        if layout.link == Some("".to_string()) {
            layout.link = None;
        }
        Ok(layout)
    }
}

impl Keys {
    pub fn qwerty() -> Self {
        Keys::new(
            vec![
                vec!['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
                vec!['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';'],
                vec!['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'],
            ],
            1,
            None,
        )
    }
    /// Returns whether the keys contain a character or not.
    /// # Example
    /// ```rust
    /// use keynergy::{Keys, Pos};
    /// let qwerty = Keys::qwerty();
    /// assert_eq!(qwerty.has_pos(Pos::new(0,0)), true);
    /// assert_eq!(qwerty.has_pos(Pos::new(100,4)), false);
    /// ```
    pub fn has_pos(&self, p: Pos) -> bool {
        #[allow(clippy::collapsible_if)]
        if self.matrix.len() > p.row {
            if self.matrix[p.row].len() > p.col {
                return true;
            }
        }
        false
    }

    /// Returns the character at the given position without checking
    /// if it's valid.
    /// # Examples
    /// ```rust
    /// use keynergy::{Keys, Pos};
    /// let qwerty = Keys::qwerty();
    /// assert_eq!(qwerty.pos_key_unsafe(Pos::new(0,0)), &'q');
    /// ```
    /// This example panics
    /// ```rust,should_panic
    /// use keynergy::{Keys, Pos};
    /// let qwerty = Keys::qwerty();
    /// let c = qwerty.pos_key_unsafe(Pos::new(100, 0));
    /// ```
    pub fn pos_key_unsafe(&self, p: Pos) -> &char {
        &self.matrix[p.row][p.col]
    }

    pub fn pos_key(&self, p: Pos) -> Option<&char> {
        if self.matrix.len() > p.row {
            let row = &self.matrix[p.row];
            if row.len() > p.col {
                return Some(&row[p.col]);
            }
        }
        None
    }

    pub fn swap(&mut self, a: Pos, b: Pos) {
        if a.row == b.row {
            self.matrix[a.row as usize].swap(a.col as usize, b.col as usize)
        } else {
            let gtr: &Pos;
            let lsr: &Pos;
            if a.row > b.row {
                gtr = &a;
                lsr = &b;
            } else {
                gtr = &b;
                lsr = &a;
            }
            let (l, r) = self.matrix.split_at_mut(gtr.row as usize);
            mem::swap(
                &mut l[lsr.row as usize][lsr.col as usize],
                &mut r[(gtr.row - lsr.row - 1) as usize][gtr.col as usize],
            )
        }

        self.map.insert(*self.pos_key_unsafe(a), a);
        self.map.insert(*self.pos_key_unsafe(b), b);
    }
}

#[cfg(test)]
mod tests {
    use crate::Layout;
    use crate::Pos;
    #[test]
    fn load_layout() {
        let semimak_jq = Layout::load("testdata/semimak_jq.toml").unwrap();
        assert_eq!(semimak_jq.name, "Semimak JQ");
        assert_eq!(semimak_jq.author, "semi");
        assert_eq!(
            semimak_jq.link.unwrap(),
            "https://semilin.github.io/semimak"
        );
        assert_eq!(semimak_jq.year, 2021);
        let keys = semimak_jq.formats.standard.unwrap();
        assert_eq!(keys.matrix[0][0], 'f');
        assert_eq!(keys.map[&'l'], Pos::new(1, 0));
        // check that map aligns with matrix
        let mut y = 0;
        for row in &keys.matrix {
            let mut x = 0;
            for key in row {
                assert_eq!(*key, keys.matrix[y][x]);
                x += 1;
            }
            y += 1;
        }
    }
    #[test]
    fn keys_swap() {
        let semimak_jq = Layout::load("testdata/semimak_jq.toml").unwrap();
        let mut keys = semimak_jq.formats.standard.unwrap();
        keys.swap(Pos::new(0, 0), Pos::new(1, 0));
        assert_eq!(keys.matrix[0][0], 'l');
        assert_eq!(keys.matrix[0][1], 'f');
        assert_eq!(keys.map[&'l'], Pos::new(0, 0));
        assert_eq!(keys.map[&'f'], Pos::new(1, 0));

        keys.swap(Pos::new(3, 0), Pos::new(2, 1));
        assert_eq!(keys.matrix[0][3], 'n');
        assert_eq!(keys.matrix[1][2], 'v');
        assert_eq!(keys.map[&'n'], Pos::new(3, 0));
        assert_eq!(keys.map[&'v'], Pos::new(2, 1));
    }
}