keygraph_rs/
lib.rs

1#[macro_use]
2extern crate lazy_static;
3extern crate petgraph;
4
5pub use petgraph::graphmap::DiGraphMap;
6
7pub type Keyboard = DiGraphMap<Key, Edge>;
8
9/// Datatype for graph nodes representing a key on the keyboard.
10#[derive(Hash, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
11pub struct Key {
12    /// Value of the key
13    pub value: char, 
14    /// Value when shift is pressed
15    pub shifted: char,
16}
17
18impl Key {
19    pub fn is_shifted(&self, val: char) -> bool {
20        self.shifted == val && val != '\0'
21    }
22
23    pub fn is_unshifted(&self, val: char) -> bool {
24        self.value == val && val != '\0'
25    }
26}
27
28#[test]
29fn is_shifted_test() {
30    let t = Key { 
31        value: 'a',
32        shifted: 'A'
33    };
34    assert!(t.is_shifted('A'));
35    assert!(t.is_unshifted('a'));
36    assert!(!t.is_shifted('a'));
37    assert!(!t.is_unshifted('A'));
38    assert!(!(t.is_shifted('Y') || t.is_unshifted('y')));
39}
40
41/// Trait to find a key given a single character from it. This function is 
42/// useful when you don't know what the locale of the keyboard is as numbers
43/// and symbols on a key can change (i.e. UK vs US)
44pub trait KeySearch {
45    /// Finds the key given a char from it. 
46    /// Returns Some(Key) if a key exists else returns None.
47    fn find_key(&self, v: char) -> Option<Key>;
48}
49
50/// Implementation of KeySearch for the graph used to hold keys
51impl KeySearch for DiGraphMap<Key, Edge> {
52    fn find_key(&self, v: char) -> Option<Key> {
53        if v == '\0' {
54            None
55        } else {
56            self.nodes().filter(|x| x.value == v || x.shifted == v).nth(0)
57        }
58    }
59}
60
61/// Enum representing a direction relative to a key on either the horizontal or
62/// vertical axis
63#[derive(Debug, PartialEq, Clone, Copy)]
64pub enum Direction {
65    /// Previous refers to above or left to the key 
66    Previous = -1, 
67    /// Next refers to below or to the right of the key
68    Next = 1, 
69    /// Same refers to the same row or column as the reference key
70    Same = 0, 
71}
72
73/// Struct to represent the relative positioning of one key to a neighbouring 
74/// key
75#[derive(Debug, PartialEq, Clone, Copy)]
76pub struct Edge {
77    /// Relative horizontal position
78    pub horizontal: Direction, 
79    /// Relative Vertical position
80    pub vertical: Direction, 
81}
82
83/// Keyboard style. The main part of a keyboard normally applies a slant to the
84/// rows meaning that a key only has 6 neighbours, however numpads are aligned
85/// meaning that they have more neighbours. This enum allows for distinguishing
86/// between physical key layouts
87#[derive(PartialEq)]
88enum KeyboardStyle {
89    /// Keys are slanted with a row offset likely applied
90    Slanted, 
91    /// Keys are aligned in a clear grid
92    Aligned, 
93}
94
95/// Returns a vector of the relative positions of the neighbours to a key on a
96/// slanted keyboard
97fn get_slanted_positions() -> Vec<Edge> {
98    use Direction::{Previous, Next, Same};
99    vec![ 
100        Edge{ horizontal: Previous, vertical: Same },
101        Edge{ horizontal: Same, vertical: Previous },
102        Edge{ horizontal: Next, vertical: Previous },
103        Edge{ horizontal: Next, vertical: Same },
104        Edge{ horizontal: Same, vertical: Next },
105        Edge{ horizontal: Previous, vertical: Next },
106    ]
107}
108
109/// Returns a vector of the relative positions of the neighbours to a key on an
110/// aligned keyboard
111fn get_aligned_positions() -> Vec<Edge> {
112    use Direction::{Previous, Next, Same};
113    vec![
114        Edge{ horizontal: Previous, vertical: Same },
115        Edge{ horizontal: Previous, vertical: Previous },
116        Edge{ horizontal: Same, vertical: Previous },
117        Edge{ horizontal: Next, vertical: Previous },
118        Edge{ horizontal: Next, vertical: Same },
119        Edge{ horizontal: Next, vertical: Next },
120        Edge{ horizontal: Same, vertical: Next },
121        Edge{ horizontal: Previous, vertical: Next },
122    ]
123}
124
125/// Keyboards exported to the user.
126lazy_static! {
127    pub static ref QWERTY_US: Keyboard = generate_qwerty_us();
128    pub static ref QWERTY_UK: Keyboard = generate_qwerty_uk();
129    pub static ref DVORAK: Keyboard = generate_dvorak(); 
130    pub static ref STANDARD_NUMPAD: Keyboard = generate_standard_numpad();
131    pub static ref MAC_NUMPAD: Keyboard = generate_mac_numpad();
132}
133
134
135/// Convenience strings to iterate over.
136static ALPHABET: &'static str = "abcdefghijklmnopqrstuvwxyz";
137static NUMBERS: &'static str = "0123456789";
138
139
140/// Function to add all alphabet characters to keyboard. (a-z & A-Z).
141/// With qwerty and dvorak unshifted is lowercase and shifted is uppercase so
142/// these keys are common.
143///
144/// This function takes a graph representing the keyboard as an argument so it
145/// can insert the nodes
146fn add_alphabetics(graph: &mut Keyboard) {
147    for c in ALPHABET.chars() {
148        graph.add_node(Key {
149            value: c,
150            shifted: c.to_uppercase().nth(0).unwrap(),
151        });
152    }
153}
154
155/// Numpads typically have no shift modifiers so use this function to populate
156/// the numeric keys.
157/// 
158/// This function takes a graph representing the keyboard as an argument so it
159/// can insert the nodes
160fn add_unshifted_number_keys(graph: &mut Keyboard) {
161
162    for c in NUMBERS.chars() {
163        graph.add_node(Key {
164            value: c,
165            shifted: '\0',
166        });
167    }
168}
169
170
171/// Given string representation of the keyboard and it's rows and a graph of
172/// nodes this function connects the edges between the nodes. 
173/// 
174/// * keyboard - string representation of the keyboard. Use line breaks to 
175///     separate rows, spaces to delimit chars and \0 on a row to represent
176///     a void area on the keyboard (lines up keys when keys are slanted)
177/// * graph - graph storing the keyboard adjacency graph
178/// * style - enum representing alignment of keys
179/// * add_missing_keys - whether missing keys should be added to the graph or 
180///     ignored
181fn connect_keyboard_nodes(keyboard: &str,
182                          graph: &mut Keyboard,
183                          style: KeyboardStyle,
184                          add_missing_keys: bool) {
185
186    let relative_positions = if style == KeyboardStyle::Slanted {
187        get_slanted_positions()
188    } else {
189        get_aligned_positions()
190    };
191    let rows = keyboard.lines()
192                       .map(|x| x.chars().filter(|y| y != &' ').collect::<Vec<char>>())
193                       .collect::<Vec<Vec<char>>>();
194
195    let rowcount = rows.iter().count() as i32;
196    for (i, row) in rows.iter().enumerate() {
197        for (j, key) in row.iter().enumerate() {
198            // Get the adjacent keys now
199            let k = graph.find_key(*key);
200            if k.is_none() && !add_missing_keys {
201                continue;
202            }
203            let k = if k.is_some() {
204                k.unwrap()
205            } else {
206                Key {
207                    value: *key,
208                    shifted: '\0',
209                }
210            };
211
212            for dir in relative_positions.iter() {
213                let y: i32 = i as i32 + dir.vertical as i32;
214                let x: i32 = j as i32 + dir.horizontal as i32;
215                if y > -1 && y < rowcount && x > -1 {
216                    let temp_row = if dir.vertical == Direction::Same {
217                        row
218                    } else {
219                        rows.get(y as usize).unwrap()
220                    };
221
222                    if let Some(temp_char) = temp_row.get(x as usize) {
223
224                        let n = graph.find_key(*temp_char);
225                        
226                        if n.is_none() && !add_missing_keys {
227                            continue;
228                        }
229
230                        let n = if n.is_some() {
231                            n.unwrap()
232                        } else {
233                            Key {
234                                value: *temp_char,
235                                shifted: '\0',
236                            }
237                        };
238            
239                        graph.add_edge(k, n, *dir);
240                    }
241                }
242            }
243        }
244    }
245}
246
247/// Any keys the user wants to specify that aren't populated by another function
248/// should be added here.
249fn add_remaining_keys(keys: Vec<Key>, graph: &mut Keyboard) {
250
251    for k in keys.iter() {
252        graph.add_node(k.clone());
253    }
254}
255
256/// Generates the graph for the qwerty US keyboard layout
257pub fn generate_qwerty_us() -> Keyboard {
258    let mut result = DiGraphMap::<Key, Edge>::new();
259    // This is a bit nasty but I don't see how to do it nicer..
260    // Trailing space after \n represents keyboard offset.
261    let qwerty_us = "` 1 2 3 4 5 6 7 8 9 0 - =\n\
262                     \0 q w e r t y u i o p [ ] \\\n\
263                     \0 a s d f g h j k l ; '\n\
264                     \0 z x c v b n m , . /";
265
266    add_alphabetics(&mut result);
267
268    let remaining_keys = vec![ 
269        Key{ value: '`', shifted: '~'},
270        Key{ value: '1', shifted: '!'},
271        Key{ value: '2', shifted: '@'},
272        Key{ value: '3', shifted: '#'},
273        Key{ value: '4', shifted: '$'},
274        Key{ value: '5', shifted: '%'},
275        Key{ value: '6', shifted: '^'},
276        Key{ value: '7', shifted: '&'},
277        Key{ value: '8', shifted: '*'},
278        Key{ value: '9', shifted: '('},
279        Key{ value: '0', shifted: ')'},
280        Key{ value: '-', shifted: '_'},
281        Key{ value: '=', shifted: '+'},
282        Key{ value: '[', shifted: '{'},
283        Key{ value: ']', shifted: '}'},
284        Key{ value: '\\', shifted: '|'},
285        Key{ value: ';', shifted: ':'},
286        Key{ value: '\'', shifted: '\"'},
287        Key{ value: ',', shifted: '<'},
288        Key{ value: '.', shifted: '>'},
289        Key{ value: '/', shifted: '?'}
290    ];
291    add_remaining_keys(remaining_keys, &mut result);
292
293    connect_keyboard_nodes(qwerty_us, &mut result, KeyboardStyle::Slanted, false);
294
295    result
296}
297
298/// Generates the graph for the qwerty US keyboard layout
299pub fn generate_qwerty_uk() -> Keyboard {
300    let mut result = DiGraphMap::<Key, Edge>::new();
301    // This is a bit nasty but I don't see how to do it nicer..
302    // Trailing space after \n represents keyboard offset.
303    let qwerty_uk = "` 1 2 3 4 5 6 7 8 9 0 - =\n\
304                     \0 q w e r t y u i o p [ ] \\\n\
305                     \0 a s d f g h j k l ; ' #\n\
306                     \0 z x c v b n m , . /";
307
308    add_alphabetics(&mut result);
309
310    let remaining_keys = vec![ 
311        Key{ value: '`', shifted: '¬'},
312        Key{ value: '1', shifted: '!'},
313        Key{ value: '2', shifted: '\"'},
314        Key{ value: '3', shifted: '£'},
315        Key{ value: '4', shifted: '$'},
316        Key{ value: '5', shifted: '%'},
317        Key{ value: '6', shifted: '^'},
318        Key{ value: '7', shifted: '&'},
319        Key{ value: '8', shifted: '*'},
320        Key{ value: '9', shifted: '('},
321        Key{ value: '0', shifted: ')'},
322        Key{ value: '-', shifted: '_'},
323        Key{ value: '=', shifted: '+'},
324        Key{ value: '[', shifted: '{'},
325        Key{ value: ']', shifted: '}'},
326        Key{ value: '\\', shifted: '|'},
327        Key{ value: ';', shifted: ':'},
328        Key{ value: '\'', shifted: '@'},
329        Key{ value: ',', shifted: '<'},
330        Key{ value: '.', shifted: '>'},
331        Key{ value: '/', shifted: '?'},
332        Key{ value: '#', shifted: '~'}
333    ];
334    add_remaining_keys(remaining_keys, &mut result);
335
336    connect_keyboard_nodes(qwerty_uk, &mut result, KeyboardStyle::Slanted, false);
337
338    result
339}
340
341/// Generates a graph for the dvorak keyboard layout
342pub fn generate_dvorak() -> Keyboard {
343    let mut result = DiGraphMap::<Key, Edge>::new();
344    // This is a bit nasty but I don't see how to do it nicer..
345    // Trailing space after \n represents keyboard offset.
346    let qwerty_us = "` 1 2 3 4 5 6 7 8 9 0 [ ]\n\
347                      \0 ' , . p y f g c r l / = \\\n\
348                      \0 a o e u i d h t n s -\n\
349                      \0 ; q j k x b m w v z";
350
351    add_alphabetics(&mut result);
352
353    let remaining_keys = vec![ 
354        Key{ value: '`', shifted: '~'},
355        Key{ value: '1', shifted: '!'},
356        Key{ value: '2', shifted: '@'},
357        Key{ value: '3', shifted: '#'},
358        Key{ value: '4', shifted: '$'},
359        Key{ value: '5', shifted: '%'},
360        Key{ value: '6', shifted: '^'},
361        Key{ value: '7', shifted: '&'},
362        Key{ value: '8', shifted: '*'},
363        Key{ value: '9', shifted: '('},
364        Key{ value: '0', shifted: ')'},
365        Key{ value: '-', shifted: '_'},
366        Key{ value: '=', shifted: '+'},
367        Key{ value: '[', shifted: '{'},
368        Key{ value: ']', shifted: '}'},
369        Key{ value: '\\', shifted: '|'},
370        Key{ value: ';', shifted: ':'},
371        Key{ value: '\'', shifted: '\"'},
372        Key{ value: ',', shifted: '<'},
373        Key{ value: '.', shifted: '>'},
374        Key{ value: '/', shifted: '?'}
375    ];
376    add_remaining_keys(remaining_keys, &mut result);
377
378    connect_keyboard_nodes(qwerty_us, &mut result, KeyboardStyle::Slanted, false);
379
380    result
381}
382
383/// Generates a standard numpad.
384pub fn generate_standard_numpad() -> Keyboard {
385    let mut result = DiGraphMap::<Key, Edge>::new();
386    let numpad = "\0 / * -\n7 8 9 +\n4 5 6\n1 2 3\n\0 0 .";
387
388    add_unshifted_number_keys(&mut result);
389
390    connect_keyboard_nodes(numpad, &mut result, KeyboardStyle::Aligned, true);
391    result
392}
393
394
395/// Generates the Apple Mac style numpad
396pub fn generate_mac_numpad() -> Keyboard {
397    let mut result = DiGraphMap::<Key, Edge>::new();
398    let numpad = "\0 = / *\n7 8 9 -\n4 5 6 +\n1 2 3\n\0 0 .";
399
400    connect_keyboard_nodes(numpad, &mut result, KeyboardStyle::Aligned, true);
401    result
402}
403
404
405#[test]
406fn test_alphabetics() {
407    assert_eq!(ALPHABET.chars().count(), 26);
408    
409    let mut result = DiGraphMap::<Key, Edge>::new();
410    add_alphabetics(&mut result);
411
412    let uppercase = ALPHABET.to_uppercase();
413    for (l, u) in ALPHABET.chars().zip(uppercase.chars()) {
414        let test = Key {
415            value: l,
416            shifted: u
417        };
418        assert!(result.contains_node(test));
419        // Get testing of trait for free
420        assert!(result.find_key(l).is_some());
421        assert!(result.find_key(u).is_some());
422    }
423}
424
425#[test]
426fn test_add_number_keys() {
427    assert_eq!(NUMBERS.chars().count(), 10);
428    
429    let mut result = DiGraphMap::<Key, Edge>::new();
430    add_unshifted_number_keys(&mut result);
431    for c in NUMBERS.chars() {
432        let test = Key {
433            value: c,
434            shifted: '\0'
435        };
436        assert!(result.contains_node(test));
437        assert!(result.find_key(c).is_some());
438    }
439    assert!(result.find_key('\0').is_none());
440}