use bevy::prelude::*;
use crate::app::i18n::Lang;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Layout {
Qwerty,
Azerty,
Qwertz,
}
impl crate::app::cycle::Cycle for Option<Layout> {
const VARIANTS: &'static [Self] = &[
None,
Some(Layout::Qwerty),
Some(Layout::Azerty),
Some(Layout::Qwertz),
];
}
impl Layout {
pub const ALL: [Layout; 3] = [Layout::Qwerty, Layout::Azerty, Layout::Qwertz];
pub fn name(self) -> &'static str {
match self {
Layout::Qwerty => "QWERTY",
Layout::Azerty => "AZERTY",
Layout::Qwertz => "QWERTZ",
}
}
pub fn key(self) -> &'static str {
match self {
Layout::Qwerty => "qwerty",
Layout::Azerty => "azerty",
Layout::Qwertz => "qwertz",
}
}
pub fn from_key(key: &str) -> Option<Layout> {
Layout::ALL.into_iter().find(|layout| layout.key() == key)
}
pub fn of(lang: Lang) -> Option<Layout> {
match lang {
Lang::Fr => Some(Layout::Azerty),
Lang::De => Some(Layout::Qwertz),
Lang::En | Lang::Es | Lang::It | Lang::Nl | Lang::Ru | Lang::Ja => None,
}
}
pub fn caps(self) -> &'static [(KeyCode, char)] {
match self {
Layout::Qwerty => &[],
Layout::Azerty => &[
(KeyCode::Backslash, '*'),
(KeyCode::BracketLeft, '^'),
(KeyCode::BracketRight, '$'),
(KeyCode::Comma, ';'),
(KeyCode::KeyA, 'Q'),
(KeyCode::KeyQ, 'A'),
(KeyCode::KeyW, 'Z'),
(KeyCode::KeyZ, 'W'),
(KeyCode::Minus, ')'),
(KeyCode::Period, ':'),
(KeyCode::Semicolon, 'M'),
(KeyCode::Slash, '!'),
],
Layout::Qwertz => &[
(KeyCode::Backquote, '^'),
(KeyCode::Backslash, '#'),
(KeyCode::BracketRight, '+'),
(KeyCode::KeyY, 'Z'),
(KeyCode::KeyZ, 'Y'),
(KeyCode::Slash, '-'),
],
}
}
pub fn tells(self) -> &'static [(KeyCode, char)] {
match self {
Layout::Qwerty => &[],
Layout::Azerty => &[
(KeyCode::KeyA, 'Q'),
(KeyCode::KeyQ, 'A'),
(KeyCode::KeyW, 'Z'),
(KeyCode::KeyZ, 'W'),
(KeyCode::Semicolon, 'M'),
],
Layout::Qwertz => &[(KeyCode::KeyY, 'Z'), (KeyCode::KeyZ, 'Y')],
}
}
pub(super) fn cap(self, key: KeyCode) -> Option<char> {
self.caps().iter().find(|(k, _)| *k == key).map(|(_, c)| *c)
}
pub(super) fn key_for(self, letter: char) -> Option<KeyCode> {
self.caps()
.iter()
.find(|(_, c)| *c == letter)
.map(|(k, _)| *k)
}
}
#[cfg(test)]
mod tests {
use super::super::learnable;
use super::*;
use crate::app::binds;
#[test]
fn every_layout_table_is_learnable_and_says_something_new() {
for layout in Layout::ALL {
let mut seen = Vec::new();
for &(key, cap) in layout.caps() {
assert!(cap.is_ascii_graphic(), "{layout:?} {key:?}");
assert!(learnable(key), "{layout:?} {key:?}");
assert_ne!(binds::key_label(key), cap.to_string(), "{layout:?}");
assert!(!seen.contains(&key), "{layout:?} spells {key:?} twice");
seen.push(key);
}
for tell in layout.tells() {
assert!(layout.caps().contains(tell), "{layout:?} {tell:?}");
}
}
}
#[test]
fn every_language_names_its_keyboard() {
assert_eq!(Layout::of(Lang::Fr), Some(Layout::Azerty));
assert_eq!(Layout::of(Lang::De), Some(Layout::Qwertz));
for lang in crate::app::i18n::ALL_LANGS {
if matches!(lang, Lang::Fr | Lang::De) {
continue;
}
assert_eq!(Layout::of(lang), None, "{lang:?}");
}
}
}