From 83afc839e7d507919d55956810f66606e1b804e2 Mon Sep 17 00:00:00 2001 From: Bastien Dejean Date: Sun, 22 Dec 2019 17:08:31 +0100 Subject: [PATCH] Externalize keyboard layouts --- dist.sh | 2 + doc/MANUAL.md | 11 +- icons/alternate.svg | 1 + icons/combine.svg | 1 + icons/move-backward-short.svg | 1 + icons/move-forward-short.svg | 1 + icons/return.svg | 1 + icons/shift.svg | 1 + keyboard-layouts/english.json | 32 +++ keyboard-layouts/russian.json | 32 +++ src/app.rs | 26 +- src/device.rs | 23 -- src/emulator.rs | 45 +--- src/geom.rs | 3 +- src/settings/mod.rs | 2 + src/view/calculator/mod.rs | 4 +- src/view/common.rs | 23 +- src/view/dictionary/mod.rs | 8 +- src/view/home/mod.rs | 4 +- src/view/icon.rs | 3 +- src/view/key.rs | 119 ++++++-- src/view/keyboard.rs | 493 +++++++++++----------------------- src/view/mod.rs | 2 + src/view/reader/mod.rs | 4 +- 24 files changed, 406 insertions(+), 436 deletions(-) create mode 100644 icons/alternate.svg create mode 100644 icons/combine.svg create mode 100644 icons/move-backward-short.svg create mode 100644 icons/move-forward-short.svg create mode 100644 icons/return.svg create mode 100644 icons/shift.svg create mode 100644 keyboard-layouts/english.json create mode 100644 keyboard-layouts/russian.json diff --git a/dist.sh b/dist.sh index a2a346b..b781c68 100755 --- a/dist.sh +++ b/dist.sh @@ -23,12 +23,14 @@ cp libs/libmupdf.so dist/libs cp libs/libmupdfwrapper.so dist/libs cp -R hyphenation-patterns dist +cp -R keyboard-layouts dist cp -R bin dist cp -R scripts dist cp -R icons dist cp -R fonts dist cp -R css dist find dist/css -name '*-user.css' -delete +find dist/keyboard-layouts -name '*-user.json' -delete cp target/arm-unknown-linux-gnueabihf/release/plato dist/ patchelf --remove-rpath dist/libs/* diff --git a/doc/MANUAL.md b/doc/MANUAL.md index b01cb0b..06ee050 100644 --- a/doc/MANUAL.md +++ b/doc/MANUAL.md @@ -95,10 +95,19 @@ Tap the page indicator to go a specific page. The *ALT* and *SHIFT* keys can be locked by tapping them twice. -The *CMB* (combine) key can be used to enter special characters, e.g.: `CMB o e` produces `œ`. +The *CMB* (combine) key can be used to enter special characters, e.g.: `CMB o e` produces `œ` (cf. [Combination Sequences](#combination-sequences)). A tap and hold on the delete or motion keys will act on words instead of characters. +Tap and hold the space bar to bring up the keyboard layouts menu. + +Keyboard layouts are described through a JSON object with the following keys: + +- *name*: displayed in the keyboard layouts menu. +- *outputs*: list of output keys for each modifier combination (*none*, *shift*, *alt*, *shift+alt*). +- *keys*: description of each key on the keyboard. The following special key names (and abbreviations) are recognized: *Shift* (*Sft*), *Return* (*Ret*), *Alternate* (*Alt*), *Combine* (*Cmb*), *MoveFwd* (*MoveF*, *MF*), *MoveBwd* (*MoveB*, *MB*), *DelFwd* (*DelF*, *DF*), *DelBwd* (*DelB*, *DB*), *Space* (*Spc*). *▢* is used to indicate an output key. +- *widths*: width/height ratio for each key. The key gap's ratio is 0.06. + # Applications Applications can be launched from the *Applications* submenu of the main menu. diff --git a/icons/alternate.svg b/icons/alternate.svg new file mode 100644 index 0000000..2d2d727 --- /dev/null +++ b/icons/alternate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/combine.svg b/icons/combine.svg new file mode 100644 index 0000000..d3ea214 --- /dev/null +++ b/icons/combine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/move-backward-short.svg b/icons/move-backward-short.svg new file mode 100644 index 0000000..dfe4aa5 --- /dev/null +++ b/icons/move-backward-short.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/move-forward-short.svg b/icons/move-forward-short.svg new file mode 100644 index 0000000..4b8b4ce --- /dev/null +++ b/icons/move-forward-short.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/return.svg b/icons/return.svg new file mode 100644 index 0000000..fd52adf --- /dev/null +++ b/icons/return.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/shift.svg b/icons/shift.svg new file mode 100644 index 0000000..72b45da --- /dev/null +++ b/icons/shift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/keyboard-layouts/english.json b/keyboard-layouts/english.json new file mode 100644 index 0000000..01317e7 --- /dev/null +++ b/keyboard-layouts/english.json @@ -0,0 +1,32 @@ +{ + "name": "English", + "outputs": [ + [["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], + ["a", "s", "d", "f", "g", "h", "j", "k", "l"], + ["z", "x", "c", "v", "b", "n", "m"]], + + [["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], + ["A", "S", "D", "F", "G", "H", "J", "K", "L"], + ["Z", "X", "C", "V", "B", "N", "M"]], + + [["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], + ["\\", "=", ",", "'", "_","\"", ".", "*", "/"], + ["!", "-", "(", ":", ")", "+", "?"]], + + [["·", "“", "%", "^", "#", "$", "~", "×", "”", "°"], + ["‘", "@", "[", "`", "|", "…", "]", "&", "’"], + ["–", "<", "{", ";", "}", ">", "—"]] + ], + "keys": [ + ["▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢"], + ["DB", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "DF"], + ["Shift", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "Return"], + ["MoveB", "Cmb", "Space", "Alt", "MoveF"] + ], + "widths": [ + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [2.06, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.06], + [1.53, 1.53, 4.18, 1.53, 1.53] + ] +} diff --git a/keyboard-layouts/russian.json b/keyboard-layouts/russian.json new file mode 100644 index 0000000..4cadb30 --- /dev/null +++ b/keyboard-layouts/russian.json @@ -0,0 +1,32 @@ +{ + "name": "Russian", + "outputs": [ + [["й", "ц", "у", "к", "е", "н", "г", "ш", "щ", "з", "х"], + ["ф", "ы", "в", "а", "п", "р", "о", "л", "д", "ж", "э"], + ["я", "ч", "с", "м", "и", "т", "ь", "б", "ю"]], + + [["Й", "Ц", "У", "К", "Е", "Н", "Г", "Ш", "Щ", "З", "Х"], + ["Ф", "Ы", "В", "А", "П", "Р", "О", "Л", "Д", "Ж", "Э"], + ["Я", "Ч", "С", "М", "И", "Т", "Ь", "Б", "Ю"]], + + [["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "№"], + ["ё","\\", "=", ",", "'", "_","\"", ".", "*", "/", "ъ"], + ["₽", "!", "-", "(", ":", ")", "+", "?", "§"]], + + [["·", "“", "%", "^", "#", "$", "~", "×", "”", "°", "≠"], + ["Ё", "‘", "@", "[", "`", "|", "…", "]", "&", "’", "Ъ"], + ["≤", "–", "<", "{", ";", "}", ">", "—", "≥"]] + ], + "keys": [ + ["▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢"], + ["▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢"], + ["Sft", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "▢", "Ret"], + ["DB", "MB", "Cmb", "Spc", "Alt", "MF", "DF"] + ], + "widths": [ + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [1.0, 1.53, 1.53, 3.12, 1.53, 1.53, 1.0] + ] +} diff --git a/src/app.rs b/src/app.rs index a6a1039..242ae4a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,9 +14,10 @@ use crate::framebuffer::{Framebuffer, KoboFramebuffer, Display, UpdateMode}; use crate::view::{View, Event, EntryId, EntryKind, ViewId, AppCmd}; use crate::view::{render, render_region, render_no_wait, render_no_wait_region, handle_event, expose}; use crate::view::common::{locate, locate_by_id, transfer_notifications, overlapping_rectangle}; -use crate::view::common::{toggle_input_history_menu}; +use crate::view::common::{toggle_input_history_menu, toggle_keyboard_layout_menu}; use crate::view::frontlight::FrontlightWindow; use crate::view::menu::{Menu, MenuKind}; +use crate::view::keyboard::{Layout}; use crate::view::dictionary::Dictionary as DictionaryApp; use crate::view::calculator::Calculator; use crate::view::sketch::Sketch; @@ -55,6 +56,7 @@ pub struct Context { pub filename: PathBuf, pub fonts: Fonts, pub dictionaries: BTreeMap, + pub keyboard_layouts: BTreeMap, pub input_history: HashMap>, pub frontlight: Box, pub battery: Box, @@ -74,9 +76,19 @@ impl Context { let dims = fb.dims(); let rotation = CURRENT_DEVICE.transformed_rotation(fb.rotation()); Context { fb, display: Display { dims, rotation }, - settings, metadata, filename, fonts, dictionaries: BTreeMap::new(), input_history: HashMap::new(), - battery, frontlight, lightsensor, notification_index: 0, kb_rect: Rectangle::default(), - plugged: false, covered: false, shared: false, online: false } + settings, metadata, filename, fonts, dictionaries: BTreeMap::new(), keyboard_layouts: BTreeMap::new(), + input_history: HashMap::new(), battery, frontlight, lightsensor, notification_index: 0, + kb_rect: Rectangle::default(), plugged: false, covered: false, shared: false, online: false } + } + + pub fn load_keyboard_layouts(&mut self) { + if let Ok(entries) = glob("keyboard-layouts/**/*.json") { + for path in entries.into_iter().filter_map(|e| e.ok()) { + if let Ok(layout) = load_json::(path) { + self.keyboard_layouts.insert(layout.name.clone(), layout); + } + } + } } pub fn load_dictionaries(&mut self) { @@ -286,6 +298,9 @@ pub fn run() -> Result<(), Error> { let mut context = build_context(Box::new(fb)).context("Can't build context.")?; + context.load_dictionaries(); + context.load_keyboard_layouts(); + let paths = vec!["/dev/input/event0".to_string(), "/dev/input/event1".to_string()]; let (raw_sender, raw_receiver) = raw_events(paths); @@ -854,6 +869,9 @@ pub fn run() -> Result<(), Error> { Event::ToggleInputHistoryMenu(id, rect) => { toggle_input_history_menu(view.as_mut(), id, rect, None, &tx, &mut context); }, + Event::ToggleNear(ViewId::KeyboardLayoutMenu, rect) => { + toggle_keyboard_layout_menu(view.as_mut(), rect, None, &tx, &mut context); + }, Event::Close(ViewId::Frontlight) => { if let Some(index) = locate::(view.as_ref()) { let rect = *view.child(index).rect(); diff --git a/src/device.rs b/src/device.rs index 20b702b..292dfd2 100644 --- a/src/device.rs +++ b/src/device.rs @@ -371,26 +371,3 @@ pub fn optimal_bars_setup(height: u32, dpi: u16) -> (u32, u32) { } result } - -pub fn optimal_key_setup(width: u32, height: u32, dpi: u16) -> (u32, u32) { - let target_side = scale_by_dpi(117.0, dpi) as u32; - let target_padding = scale_by_dpi(6.0, dpi) as u32; - let minimum_side = target_side / 3; - let minimum_padding = 4 * target_padding / 5; - let mut max_score = 0; - let mut result = (0, 0); - for side in minimum_side..=target_side { - for padding in minimum_padding..=target_padding { - let w = 11 * side + 12 * padding; - let h = 4 * side + 5 * padding; - if w <= width && h <= height { - let score = side + padding; - if score > max_score { - result = (side, padding); - max_score = score; - } - } - } - } - result -} diff --git a/src/emulator.rs b/src/emulator.rs index b8a357b..a530f90 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -44,13 +44,12 @@ use crate::view::home::Home; use crate::view::reader::Reader; use crate::view::notification::Notification; use crate::view::frontlight::FrontlightWindow; -use crate::view::keyboard::Keyboard; use crate::view::menu::{Menu, MenuKind}; use crate::view::dictionary::Dictionary; use crate::view::calculator::Calculator; use crate::view::sketch::Sketch; use crate::view::common::{locate, locate_by_id, transfer_notifications, overlapping_rectangle}; -use crate::view::common::{toggle_input_history_menu}; +use crate::view::common::{toggle_input_history_menu, toggle_keyboard_layout_menu}; use crate::helpers::{load_json, save_json, load_toml, save_toml}; use crate::metadata::{Metadata, METADATA_FILENAME, auto_import}; use crate::settings::{Settings, SETTINGS_PATH}; @@ -226,6 +225,9 @@ pub fn run() -> Result<(), Error> { let mut context = build_context(Box::new(fb))?; + context.load_dictionaries(); + context.load_keyboard_layouts(); + let (tx, rx) = mpsc::channel(); let (ty, ry) = mpsc::channel(); let touch_screen = gesture_events(ry); @@ -278,42 +280,6 @@ pub fn run() -> Result<(), Error> { break; }, SdlEvent::KeyDown { scancode: Some(scancode), .. } => { - if let Some(kb_idx) = locate::(view.as_ref()) { - let index = match scancode { - Scancode::Backspace => Some(10), - Scancode::Delete => Some(20), - Scancode::LShift | Scancode::RShift => Some(21), - Scancode::Return => Some(29), - Scancode::Left => Some(30), - Scancode::LGui | Scancode::RGui => Some(31), - Scancode::Space => Some(32), - Scancode::LAlt | Scancode::RAlt => Some(33), - Scancode::Right => Some(34), - _ => { - let name = scancode.name(); - if name.len() == 1 { - let c = name.chars().next().unwrap() - .to_lowercase().next().unwrap(); - if let Some(i) = "qwertyuiop".find(c) { - Some(i) - } else if let Some(i) = "asdfghjkl".find(c) { - Some(11+i) - } else if let Some(i) = "zxcvbnm".find(c) { - Some(22+i) - } else { - None - } - } else { - None - } - }, - }; - if index.is_some() { - let position = view.child(kb_idx).child(index.unwrap()).rect().center(); - ty.send(DeviceEvent::Finger { status: FingerStatus::Down, position, id: 0, time: 0.0}).ok(); - ty.send(DeviceEvent::Finger { status: FingerStatus::Up, position, id: 0, time: 0.0}).ok(); - } - } match scancode { Scancode::LeftBracket => { let rot = (3 + context.display.rotation) % 4; @@ -449,6 +415,9 @@ pub fn run() -> Result<(), Error> { Event::ToggleInputHistoryMenu(id, rect) => { toggle_input_history_menu(view.as_mut(), id, rect, None, &tx, &mut context); }, + Event::ToggleNear(ViewId::KeyboardLayoutMenu, rect) => { + toggle_keyboard_layout_menu(view.as_mut(), rect, None, &tx, &mut context); + }, Event::Close(ViewId::Frontlight) => { if let Some(index) = locate::(view.as_ref()) { let rect = *view.child(index).rect(); diff --git a/src/geom.rs b/src/geom.rs index 6ee56db..a1702d6 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -1,3 +1,4 @@ +use serde_derive::{Serialize, Deserialize}; use std::cmp::Ordering; use std::f32::{self, consts}; use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign}; @@ -30,7 +31,7 @@ pub enum CycleDir { Previous, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum LinearDir { Backward, Forward, diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 16e4fd2..47c327d 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -43,6 +43,7 @@ pub enum ButtonScheme { #[serde(default, rename_all = "kebab-case")] pub struct Settings { pub library_path: PathBuf, + pub keyboard_layout: String, pub frontlight: bool, pub wifi: bool, pub sleep_cover: bool, @@ -282,6 +283,7 @@ impl Default for Settings { fn default() -> Self { Settings { library_path: CURRENT_DEVICE.library_path(), + keyboard_layout: "English".to_string(), frontlight: true, wifi: false, sleep_cover: true, diff --git a/src/view/calculator/mod.rs b/src/view/calculator/mod.rs index a2ca66c..4115349 100644 --- a/src/view/calculator/mod.rs +++ b/src/view/calculator/mod.rs @@ -18,7 +18,7 @@ use self::input_bar::InputBar; use self::bottom_bar::BottomBar; use self::code_area::CodeArea; use crate::view::top_bar::TopBar; -use crate::view::keyboard::{Keyboard, DEFAULT_LAYOUT}; +use crate::view::keyboard::Keyboard; use crate::view::menu::{Menu, MenuKind}; use crate::view::common::{locate_by_id}; use crate::view::common::{toggle_main_menu, toggle_battery_menu, toggle_clock_menu}; @@ -140,7 +140,7 @@ impl Calculator { rect.max.x, rect.max.y - small_height as i32 - small_thickness]; - let keyboard = Keyboard::new(&mut kb_rect, DEFAULT_LAYOUT.clone(), true, context); + let keyboard = Keyboard::new(&mut kb_rect, true, context); let sp_rect = rect![rect.min.x, kb_rect.min.y - thickness, rect.max.x, kb_rect.min.y]; diff --git a/src/view/common.rs b/src/view/common.rs index 2f6bc71..fba8164 100644 --- a/src/view/common.rs +++ b/src/view/common.rs @@ -172,7 +172,7 @@ pub fn toggle_clock_menu(view: &mut dyn View, rect: Rectangle, enable: Option, hub: &Hub, context: &mut Context) { - if let Some(index) = locate_by_id(view, ViewId::ClockMenu) { + if let Some(index) = locate_by_id(view, ViewId::InputHistoryMenu) { if let Some(true) = enable { return; } @@ -194,3 +194,24 @@ pub fn toggle_input_history_menu(view: &mut dyn View, id: ViewId, rect: Rectangl } } } + +pub fn toggle_keyboard_layout_menu(view: &mut dyn View, rect: Rectangle, enable: Option, hub: &Hub, context: &mut Context) { + if let Some(index) = locate_by_id(view, ViewId::KeyboardLayoutMenu) { + if let Some(true) = enable { + return; + } + hub.send(Event::Expose(*view.child(index).rect(), UpdateMode::Gui)).ok(); + view.children_mut().remove(index); + } else { + if let Some(false) = enable { + return; + } + let entries = context.keyboard_layouts.keys() + .map(|s| EntryKind::Command(s.to_string(), + EntryId::SetKeyboardLayout(s.to_string()))) + .collect::>(); + let keyboard_layout_menu = Menu::new(rect, ViewId::KeyboardLayoutMenu, MenuKind::Contextual, entries, context); + hub.send(Event::Render(*keyboard_layout_menu.rect(), UpdateMode::Gui)).ok(); + view.children_mut().push(Box::new(keyboard_layout_menu) as Box); + } +} diff --git a/src/view/dictionary/mod.rs b/src/view/dictionary/mod.rs index 0193e30..9495a4a 100644 --- a/src/view/dictionary/mod.rs +++ b/src/view/dictionary/mod.rs @@ -19,7 +19,7 @@ use crate::app::Context; use crate::view::filler::Filler; use crate::view::named_input::NamedInput; use crate::view::image::Image; -use crate::view::keyboard::{Keyboard, DEFAULT_LAYOUT}; +use crate::view::keyboard::Keyboard; use crate::view::menu::{Menu, MenuKind}; use crate::view::search_bar::SearchBar; use crate::view::top_bar::TopBar; @@ -85,10 +85,6 @@ fn query_to_content(query: &str, language: &String, fuzzy: bool, target: Option< impl Dictionary { pub fn new(rect: Rectangle, query: &str, language: &str, hub: &Hub, context: &mut Context) -> Dictionary { - if context.dictionaries.is_empty() { - context.load_dictionaries(); - } - let mut children = Vec::new(); let dpi = CURRENT_DEVICE.dpi; let (_, height) = context.display.dims; @@ -276,7 +272,7 @@ impl Dictionary { let number = id == Some(ViewId::GoToPageInput); let index = locate::(self).unwrap() + 1; - let keyboard = Keyboard::new(&mut kb_rect, DEFAULT_LAYOUT.clone(), number, context); + let keyboard = Keyboard::new(&mut kb_rect, number, context); self.children.insert(index, Box::new(keyboard) as Box); let separator = Filler::new(rect![self.rect.min.x, kb_rect.min.y - thickness, diff --git a/src/view/home/mod.rs b/src/view/home/mod.rs index dfa0410..e1c7b9b 100644 --- a/src/view/home/mod.rs +++ b/src/view/home/mod.rs @@ -24,7 +24,7 @@ use crate::settings::{Hook, SecondColumn}; use crate::view::filler::Filler; use crate::view::common::{locate, locate_by_id}; use crate::view::common::{toggle_main_menu, toggle_battery_menu, toggle_clock_menu}; -use crate::view::keyboard::{Keyboard, DEFAULT_LAYOUT}; +use crate::view::keyboard::Keyboard; use crate::view::named_input::NamedInput; use crate::view::menu::{Menu, MenuKind}; use crate::view::menu_entry::MenuEntry; @@ -558,7 +558,7 @@ impl Home { _ => false, }; - let keyboard = Keyboard::new(&mut kb_rect, DEFAULT_LAYOUT.clone(), number, context); + let keyboard = Keyboard::new(&mut kb_rect, number, context); self.children.insert(index, Box::new(keyboard) as Box); let separator = Filler::new(rect![self.rect.min.x, kb_rect.min.y - thickness, diff --git a/src/view/icon.rs b/src/view/icon.rs index 45c3a32..612eca3 100644 --- a/src/view/icon.rs +++ b/src/view/icon.rs @@ -23,7 +23,8 @@ lazy_static! { let dir = Path::new("icons"); for name in ["home", "search", "back", "frontlight", "frontlight-disabled", "menu", "angle-left", "angle-right", "angle-left-small", "angle-right-small", - "delete-backward", "delete-forward", "move-backward", "move-forward", + "return", "shift", "combine", "alternate", "delete-backward", "delete-forward", + "move-backward", "move-backward-short", "move-forward", "move-forward-short", "close", "check_mark-small", "check_mark","check_mark-large", "bullet", "arrow-left", "arrow-right", "double_angle-left", "double_angle-right", "angle-down", "angle-up", "plus", "minus", "crop", "toc", "font_family", diff --git a/src/view/key.rs b/src/view/key.rs index 0411759..ce6bbc8 100644 --- a/src/view/key.rs +++ b/src/view/key.rs @@ -2,12 +2,12 @@ use crate::device::CURRENT_DEVICE; use crate::framebuffer::{Framebuffer, UpdateMode}; use crate::input::{DeviceEvent, FingerStatus}; use crate::gesture::GestureEvent; -use super::{View, Event, KeyboardEvent, Hub, Bus, TextKind}; +use super::{View, Event, ViewId, KeyboardEvent, Hub, Bus, TextKind}; use super::BORDER_RADIUS_LARGE; use super::icon::ICONS_PIXMAPS; use crate::color::{TEXT_NORMAL, TEXT_INVERTED_HARD, KEYBOARD_BG}; use crate::font::{Fonts, font_from_style, KBD_CHAR, KBD_LABEL}; -use crate::geom::{Rectangle, LinearDir, CornerSpec, halves}; +use crate::geom::{Rectangle, LinearDir, CornerSpec}; use crate::app::Context; use crate::unit::scale_by_dpi; @@ -22,6 +22,66 @@ pub enum KeyKind { Alternate, } +use std::fmt; +use serde::{Deserializer, Deserialize}; +use serde::de::{self, Visitor}; + +impl<'de> Deserialize<'de> for KeyKind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FieldVisitor; + + const FIELDS: &[&str] = &[ + "Shift", "Sft", + "Return", "Ret", + "Alternate", "Alt", + "Combine", "Cmb", + "MoveFwd", "MoveF", "MF", + "MoveBwd", "MoveB", "MB", + "DelFwd", "DelF", "DF", + "DelBwd", "DelB", "DB", + "Space", "Spc", + ]; + + impl<'de> Visitor<'de> for FieldVisitor { + type Value = KeyKind; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a key name or a single character") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "Shift" | "Sft" => Ok(KeyKind::Shift), + "Return" | "Ret" => Ok(KeyKind::Return), + "Alternate" | "Alt" => Ok(KeyKind::Alternate), + "Combine" | "Cmb" => Ok(KeyKind::Combine), + "MoveFwd" | "MoveF" | "MF" => Ok(KeyKind::Move(LinearDir::Forward)), + "MoveBwd" | "MoveB" | "MB" => Ok(KeyKind::Move(LinearDir::Backward)), + "DelFwd" | "DelF" | "DF" => Ok(KeyKind::Delete(LinearDir::Forward)), + "DelBwd" | "DelB" | "DB" => Ok(KeyKind::Delete(LinearDir::Backward)), + "Space" | "Spc" => Ok(KeyKind::Output(' ')), + _ => { + if value.chars().count() != 1 { + return Err(serde::de::Error::unknown_field(value, FIELDS)); + } + value.chars().next().map(|c| KeyKind::Output(c)) + .ok_or_else(|| serde::de::Error::custom("impossible")) + }, + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } +} + + #[derive(Clone, Debug)] pub enum KeyLabel { Char(char), @@ -30,7 +90,14 @@ pub enum KeyLabel { } impl KeyKind { - pub fn label(self) -> KeyLabel { + pub fn is_variable_output(&self) -> bool { + match self { + KeyKind::Output(ch) if *ch != ' ' => true, + _ => false + } + } + + pub fn label(self, ratio: f32) -> KeyLabel { match self { KeyKind::Output(ch) => KeyLabel::Char(ch), KeyKind::Delete(dir) => { @@ -41,14 +108,14 @@ impl KeyKind { }, KeyKind::Move(dir) => { match dir { - LinearDir::Forward => KeyLabel::Icon("move-forward"), - LinearDir::Backward => KeyLabel::Icon("move-backward"), + LinearDir::Forward => KeyLabel::Icon(if ratio <= 1.0 { "move-forward-short" } else { "move-forward" }), + LinearDir::Backward => KeyLabel::Icon(if ratio <= 1.0 { "move-backward-short" } else { "move-backward"}), } }, - KeyKind::Shift => KeyLabel::Text("SHIFT"), - KeyKind::Return => KeyLabel::Text("RETURN"), - KeyKind::Combine => KeyLabel::Text("CMB"), - KeyKind::Alternate => KeyLabel::Text("ALT"), + KeyKind::Shift => if ratio < 2.0 { KeyLabel::Icon("shift") } else { KeyLabel::Text("SHIFT") }, + KeyKind::Return => if ratio < 2.0 { KeyLabel::Icon("return") } else { KeyLabel::Text("RETURN") }, + KeyKind::Combine => if ratio <= 1.0 { KeyLabel::Icon("combine") } else { KeyLabel::Text("CMB") }, + KeyKind::Alternate => if ratio <= 1.0 { KeyLabel::Icon("alternate") } else { KeyLabel::Text("ALT") }, } } } @@ -57,23 +124,25 @@ pub struct Key { rect: Rectangle, children: Vec>, kind: KeyKind, - padding: u32, pressure: u8, active: bool, } impl Key { - pub fn new(rect: Rectangle, kind: KeyKind, padding: u32) -> Key { + pub fn new(rect: Rectangle, kind: KeyKind) -> Key { Key { rect, children: vec![], kind, - padding, pressure: 0, active: false, } } + pub fn kind(&self) -> &KeyKind { + &self.kind + } + pub fn update(&mut self, kind: KeyKind, hub: &Hub) { self.kind = kind; hub.send(Event::Render(self.rect, UpdateMode::Gui)).ok(); @@ -128,6 +197,7 @@ impl View for Key { match self.kind { KeyKind::Delete(dir) => { hub.send(Event::Keyboard(KeyboardEvent::Delete { target: TextKind::Word, dir })).ok(); }, KeyKind::Move(dir) => { hub.send(Event::Keyboard(KeyboardEvent::Move { target: TextKind::Word, dir })).ok(); }, + KeyKind::Output(' ') => { hub.send(Event::ToggleNear(ViewId::KeyboardLayoutMenu, self.rect)).ok(); }, _ => (), }; true @@ -146,17 +216,16 @@ impl View for Key { }; let border_radius = scale_by_dpi(BORDER_RADIUS_LARGE, dpi) as i32; - let (small_half_padding, big_half_padding) = halves(self.padding as i32); - let key_rect = rect![self.rect.min + big_half_padding, self.rect.max - small_half_padding]; - fb.draw_rounded_rectangle(&key_rect, &CornerSpec::Uniform(border_radius), scheme[0]); + fb.draw_rounded_rectangle(&self.rect, &CornerSpec::Uniform(border_radius), scheme[0]); + let ratio = self.rect.width() as f32 / self.rect.height() as f32; - match self.kind.label() { + match self.kind.label(ratio) { KeyLabel::Char(ch) => { let font = font_from_style(fonts, &KBD_CHAR, dpi); let plan = font.plan(&ch.to_string(), None, None); - let dx = (key_rect.width() - plan.width) as i32 / 2; - let dy = (key_rect.height() - font.x_heights.0) as i32 / 2; - let pt = pt!(key_rect.min.x + dx, key_rect.max.y - dy); + let dx = (self.rect.width() - plan.width) as i32 / 2; + let dy = (self.rect.height() - font.x_heights.0) as i32 / 2; + let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy); font.render(fb, scheme[1], &plan, pt); }, KeyLabel::Text(label) => { @@ -164,16 +233,16 @@ impl View for Key { let mut plan = font.plan(label, None, None); let letter_spacing = scale_by_dpi(4.0, dpi) as u32; plan.space_out(letter_spacing); - let dx = (key_rect.width() - plan.width) as i32 / 2; - let dy = (key_rect.height() - font.x_heights.1) as i32 / 2; - let pt = pt!(key_rect.min.x + dx, key_rect.max.y - dy); + let dx = (self.rect.width() - plan.width) as i32 / 2; + let dy = (self.rect.height() - font.x_heights.1) as i32 / 2; + let pt = pt!(self.rect.min.x + dx, self.rect.max.y - dy); font.render(fb, scheme[1], &plan, pt); }, KeyLabel::Icon(name) => { let pixmap = ICONS_PIXMAPS.get(name).unwrap(); - let dx = (key_rect.width() as i32 - pixmap.width as i32) / 2; - let dy = (key_rect.height() as i32 - pixmap.height as i32) / 2; - let pt = key_rect.min + pt!(dx, dy); + let dx = (self.rect.width() as i32 - pixmap.width as i32) / 2; + let dy = (self.rect.height() as i32 - pixmap.height as i32) / 2; + let pt = self.rect.min + pt!(dx, dy); fb.draw_blended_pixmap(pixmap, pt, scheme[1]); } } diff --git a/src/view/keyboard.rs b/src/view/keyboard.rs index 5bbb3fe..5bbe3da 100644 --- a/src/view/keyboard.rs +++ b/src/view/keyboard.rs @@ -1,32 +1,30 @@ use fnv::FnvHashMap; use lazy_static::lazy_static; -use serde_derive::{Serialize, Deserialize}; +use serde_derive::Deserialize; use crate::device::{CURRENT_DEVICE, BAR_SIZES}; -use crate::framebuffer::Framebuffer; +use crate::framebuffer::{Framebuffer, UpdateMode}; use crate::gesture::GestureEvent; use crate::input::DeviceEvent; -use super::{View, Event, Hub, Bus, KeyboardEvent, TextKind}; -use super::filler::Filler; +use super::{View, Event, Hub, Bus, KeyboardEvent, EntryId, TextKind}; use super::key::{Key, KeyKind}; use crate::color::KEYBOARD_BG; use crate::font::Fonts; use crate::app::Context; -use crate::geom::{Rectangle, LinearDir, halves}; +use crate::geom::Rectangle; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Layout { - name: String, - outputs: [OutputKeys; 4], -} +pub type Keys = Vec>; +const PADDING_RATIO: f32 = 0.06; -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub struct OutputKeys { - row1: [T; 10], - row2: [T; 9], - row3: [T; 7], +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Layout { + pub name: String, + pub outputs: [Vec>; 4], + pub keys: Vec>, + pub widths: Vec>, } -#[derive(Default)] +#[derive(Default, Debug)] pub struct State { shift: u8, alternate: u8, @@ -41,30 +39,12 @@ pub struct Keyboard { combine_buffer: String, } -use crate::device::optimal_key_setup; - impl Keyboard { - pub fn new(rect: &mut Rectangle, layout: Layout, number: bool, context: &mut Context) -> Keyboard { + pub fn new(rect: &mut Rectangle, number: bool, context: &mut Context) -> Keyboard { let mut children = Vec::new(); let dpi = CURRENT_DEVICE.dpi; - let (side, padding) = optimal_key_setup(rect.width(), rect.height(), dpi); - - let (_, height) = context.display.dims; - let &(_, big_height) = BAR_SIZES.get(&(height, dpi)).unwrap(); - let height_gap = (rect.height() - (4 * side + 5 * padding)) / big_height; - rect.min.y += (height_gap * big_height) as i32; - context.kb_rect = *rect; - - let normal_side = (side + padding) as i32; - let (small_half_side, big_half_side) = halves(normal_side); - let (small_medium_length, big_medium_length) = halves(3 * normal_side); - let large_length = 2 * normal_side; - let huge_length = 2 * large_length; - let remaining_width = rect.width() as i32 - 11 * normal_side; - let remaining_height = rect.height() as i32 - 4 * normal_side; - let (small_half_remaining_width, big_half_remaining_width) = halves(remaining_width); - let (small_half_remaining_height, big_half_remaining_height) = halves(remaining_height); + let layout = context.keyboard_layouts[&context.settings.keyboard_layout].clone(); let mut state = State::default(); @@ -72,146 +52,61 @@ impl Keyboard { state.alternate = 2; } - let mut index = 0; + let mut level = 0; if state.shift > 0 { - index += 1; + level += 1; } if state.alternate > 0 { - index += 2; - } - - // Row 1 - - for i in 0..10usize { - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + i as i32 * normal_side, - small_half_remaining_height); - let ch = layout.outputs[index].row1[i]; - let key = Key::new(rect![min_pt, min_pt + normal_side], - KeyKind::Output(ch), padding); - children.push(Box::new(key) as Box); - } - - // Row 2 - - let min_pt = rect.min + pt!(small_half_remaining_width, small_half_remaining_height + normal_side); - let key = Key::new(rect![min_pt, min_pt + normal_side], KeyKind::Delete(LinearDir::Backward), padding); - children.push(Box::new(key) as Box); - - for i in 0..9usize { - let min_pt = rect.min + pt!(small_half_remaining_width + (i + 1) as i32 * normal_side, - small_half_remaining_height + normal_side); - let ch = layout.outputs[index].row2[i]; - let key = Key::new(rect![min_pt, min_pt + normal_side], - KeyKind::Output(ch), padding); - children.push(Box::new(key) as Box); - } - - let min_pt = rect.min + pt!(small_half_remaining_width + 10 * normal_side, small_half_remaining_height + normal_side); - let key = Key::new(rect![min_pt, min_pt + normal_side], KeyKind::Delete(LinearDir::Forward), padding); - children.push(Box::new(key) as Box); - - // Row 3 - - let min_pt = rect.min + pt!(small_half_remaining_width, small_half_remaining_height + 2 * normal_side); - let key = Key::new(rect![min_pt, min_pt + pt!(large_length, normal_side)], KeyKind::Shift, padding); - children.push(Box::new(key) as Box); - - for i in 0..7usize { - let min_pt = rect.min + pt!(small_half_remaining_width + (i + 2) as i32 * normal_side, - small_half_remaining_height + 2 * normal_side); - let ch = layout.outputs[index].row3[i]; - let key = Key::new(rect![min_pt, min_pt + normal_side], - KeyKind::Output(ch), padding); - children.push(Box::new(key) as Box); + level += 2; } - let min_pt = rect.min + pt!(small_half_remaining_width + 9 * normal_side, small_half_remaining_height + 2 * normal_side); - let key = Key::new(rect![min_pt, min_pt + pt!(large_length, normal_side)], KeyKind::Return, padding); - children.push(Box::new(key) as Box); - - // Row 4 + let max_width = layout.widths.iter().map(|row| (row.len() + 1) as f32 * PADDING_RATIO + row.iter().sum::()) + .max_by(|a, b| a.partial_cmp(&b).expect("Found NaNs")) + .expect("Missing row widths"); - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side, small_half_remaining_height + 3 * normal_side); - let key = Key::new(rect![min_pt, min_pt + pt!(small_medium_length, normal_side)], KeyKind::Move(LinearDir::Backward), padding); - children.push(Box::new(key) as Box); + let kh_1 = (rect.width() as f32) / max_width; + let rows_count = layout.keys.len(); + let kh_2 = (rect.height() as f32) / (rows_count as f32 + PADDING_RATIO * (rows_count + 1) as f32); + let key_height = kh_1.min(kh_2); + let padding = PADDING_RATIO * key_height; - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + small_medium_length, small_half_remaining_height + 3 * normal_side); - let key = Key::new(rect![min_pt, min_pt + pt!(big_medium_length, normal_side)], KeyKind::Combine, padding); - children.push(Box::new(key) as Box); - - // Space bar - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + 3 * normal_side, small_half_remaining_height + 3 * normal_side); - let key = Key::new(rect![min_pt, min_pt + pt!(huge_length, normal_side)], KeyKind::Output(' '), padding); - children.push(Box::new(key) as Box); + let rows_height = key_height * rows_count as f32 + padding * (rows_count + 1) as f32; + let (_, height) = context.display.dims; + let &(_, big_height) = BAR_SIZES.get(&(height, dpi)).unwrap(); + let height_gap = (rect.height() - rows_height.round() as u32) / big_height; + rect.min.y += (height_gap * big_height) as i32; + context.kb_rect = *rect; - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + 7 * normal_side, small_half_remaining_height + 3 * normal_side); - let mut key = Key::new(rect![min_pt, min_pt + pt!(big_medium_length, normal_side)], KeyKind::Alternate, padding); - if number { - key.lock(); + let start_y = rect.min.y as f32 + padding + (rect.height() as f32 - rows_height) / 2.0; + + for (i, row) in layout.keys.iter().enumerate() { + let y = start_y + i as f32 * (padding + key_height); + let row_width = (layout.widths[i].len() + 1) as f32 * padding + layout.widths[i].iter().sum::() * key_height; + let start_x = rect.min.x as f32 + padding + (rect.width() as f32 - row_width) / 2.0; + let mut dx = 0.0; + let mut dj = 0; + + for (j, kind) in row.iter().enumerate() { + let key_width = layout.widths[i][j] * key_height; + let x = start_x + dx; + dx += key_width + padding; + let key_rect = rect![x.round() as i32, + y.round() as i32, + (x + key_width).round() as i32, + (y + key_height).round() as i32]; + let kind = match kind { + KeyKind::Output(c) if *c != ' ' => KeyKind::Output(layout.outputs[level][i][j-dj]), + _ => { dj = j + 1; kind.clone() }, + }; + let mut key = Key::new(key_rect, kind); + if number && kind == KeyKind::Alternate { + key.lock(); + } + children.push(Box::new(key) as Box); + } } - children.push(Box::new(key) as Box); - - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + 7 * normal_side + big_medium_length, small_half_remaining_height + 3 * normal_side); - let key = Key::new(rect![min_pt, min_pt + pt!(small_medium_length, normal_side)], KeyKind::Move(LinearDir::Forward), padding); - children.push(Box::new(key) as Box); - - // Boundary Fillers - let filler = Filler::new(rect![rect.min, - pt!(rect.max.x - big_half_remaining_width, - rect.min.y + small_half_remaining_height)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - let filler = Filler::new(rect![pt!(rect.max.x - big_half_remaining_width, - rect.min.y), - pt!(rect.max.x, - rect.max.y - big_half_remaining_height)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - let filler = Filler::new(rect![pt!(rect.min.x + small_half_remaining_width, - rect.max.y - big_half_remaining_height), - rect.max], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - let filler = Filler::new(rect![pt!(rect.min.x, - rect.min.y + small_half_remaining_height), - pt!(rect.min.x + small_half_remaining_width, - rect.max.y)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - // In-between Fillers - let min_pt = pt!(rect.min.x + small_half_remaining_width, - rect.min.y + small_half_remaining_height); - let filler = Filler::new(rect![min_pt, min_pt + pt!(small_half_side, - normal_side)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - let min_pt = pt!(rect.min.x + small_half_remaining_width, - rect.max.y - big_half_remaining_height - normal_side); - let filler = Filler::new(rect![min_pt, min_pt + pt!(small_half_side, - normal_side)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - let min_pt = pt!(rect.max.x - big_half_remaining_width - big_half_side, - rect.min.y + small_half_remaining_height); - let filler = Filler::new(rect![min_pt, min_pt + pt!(big_half_side, - normal_side)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); - - let min_pt = pt!(rect.max.x - big_half_remaining_width - big_half_side, - rect.max.y - big_half_remaining_height - normal_side); - let filler = Filler::new(rect![min_pt, min_pt + pt!(big_half_side, - normal_side)], - KEYBOARD_BG); - children.push(Box::new(filler) as Box); Keyboard { rect: *rect, @@ -223,34 +118,31 @@ impl Keyboard { } fn update(&mut self, hub: &Hub) { - let mut index = 0; + let mut level = 0; if self.state.shift > 0 { - index += 1; + level += 1; } if self.state.alternate > 0 { - index += 2; + level += 2; } - for i in 0..10usize { - if let Some(child) = self.children[i].as_mut().downcast_mut::() { - let ch = self.layout.outputs[index].row1[i]; - child.update(KeyKind::Output(ch), hub); - } - } + let mut index = 0; - for i in 0..9usize { - if let Some(child) = self.children[i+11].as_mut().downcast_mut::() { - let ch = self.layout.outputs[index].row2[i]; - child.update(KeyKind::Output(ch), hub); - } - } + for (i, row) in self.layout.keys.iter().enumerate() { + let mut dj = 0; - for i in 0..7usize { - if let Some(child) = self.children[i+22].as_mut().downcast_mut::() { - let ch = self.layout.outputs[index].row3[i]; - child.update(KeyKind::Output(ch), hub); + for (j, kind) in row.iter().enumerate() { + if kind.is_variable_output() { + if let Some(child) = self.children[index].downcast_mut::() { + let ch = self.layout.outputs[level][i][j-dj]; + child.update(KeyKind::Output(ch), hub); + } + } else { + dj = j + 1; + } + index += 1; } } } @@ -259,33 +151,51 @@ impl Keyboard { if self.state.shift != 1 && self.state.alternate != 1 { return; } + if self.state.shift == 1 { self.state.shift = 0; - if let Some(child) = self.children[21].as_mut().downcast_mut::() { - child.release(hub); + for child in self.children_mut() { + if let Some(key) = child.downcast_mut::() { + if *key.kind() == KeyKind::Shift { + key.release(hub); + break; + } + } } } + if self.state.alternate == 1 { self.state.alternate = 0; - if let Some(child) = self.children[33].as_mut().downcast_mut::() { - child.release(hub); + for child in self.children_mut() { + if let Some(key) = child.downcast_mut::() { + if *key.kind() == KeyKind::Alternate { + key.release(hub); + break; + } + } } } + self.update(hub); } fn release_combine(&mut self, hub: &Hub) { self.state.combine = false; self.combine_buffer.clear(); - if let Some(child) = self.children[31].as_mut().downcast_mut::() { - child.release(hub); + for child in self.children_mut() { + if let Some(key) = child.downcast_mut::() { + if *key.kind() == KeyKind::Combine { + key.release(hub); + break; + } + } } } } impl View for Keyboard { - fn handle_event(&mut self, evt: &Event, hub: &Hub, _bus: &mut Bus, _context: &mut Context) -> bool { + fn handle_event(&mut self, evt: &Event, hub: &Hub, _bus: &mut Bus, context: &mut Context) -> bool { match *evt { Event::Key(k) => { match k { @@ -328,6 +238,15 @@ impl View for Keyboard { }; true }, + Event::Select(EntryId::SetKeyboardLayout(ref name)) => { + if *name != context.settings.keyboard_layout { + context.settings.keyboard_layout = name.to_string(); + // FIXME: the keyboard's height might change, in which case, we shall notify the root view. + *self = Keyboard::new(&mut self.rect, self.state.alternate == 2, context); + hub.send(Event::Render(self.rect, UpdateMode::Gui)).ok(); + } + true + }, Event::Gesture(GestureEvent::Tap(center)) | Event::Gesture(GestureEvent::HoldFingerShort(center, ..)) if self.rect.includes(center) => true, Event::Gesture(GestureEvent::Swipe { start, .. }) if self.rect.includes(start) => true, @@ -338,139 +257,79 @@ impl View for Keyboard { fn might_skip(&self, evt: &Event) -> bool { match *evt { - Event::Key(..) | Event::Gesture(..) | Event::Device(DeviceEvent::Finger { .. }) => false, + Event::Key(..) | + Event::Gesture(..) | + Event::Device(DeviceEvent::Finger { .. }) | + Event::Select(..) => false, _ => true, } } - // TODO: draw background and remove fillers - fn render(&self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts) { + fn render(&self, fb: &mut dyn Framebuffer, rect: Rectangle, _fonts: &mut Fonts) { + for child in &self.children { + if *child.rect() == rect { + return; + } + } + + if let Some(region) = rect.intersection(&self.rect) { + fb.draw_rectangle(®ion, KEYBOARD_BG); + } + } + + fn render_rect(&self, rect: &Rectangle) -> Rectangle { + rect.intersection(&self.rect) + .unwrap_or(self.rect) } fn resize(&mut self, mut rect: Rectangle, hub: &Hub, context: &mut Context) { let dpi = CURRENT_DEVICE.dpi; - let (side, padding) = optimal_key_setup(rect.width(), rect.height(), dpi); + let max_width = self.layout.widths.iter().map(|row| (row.len() + 1) as f32 * PADDING_RATIO + row.iter().sum::()) + .max_by(|a, b| a.partial_cmp(&b).expect("Found NaNs")) + .expect("Missing row widths"); + let kh_1 = (rect.width() as f32) / max_width; + let rows_count = self.layout.keys.len(); + let kh_2 = (rect.height() as f32) / (rows_count as f32 + PADDING_RATIO * (rows_count + 1) as f32); + let key_height = kh_1.min(kh_2); + let padding = PADDING_RATIO * key_height; + + let rows_height = key_height * rows_count as f32 + padding * (rows_count + 1) as f32; let (_, height) = context.display.dims; let &(_, big_height) = BAR_SIZES.get(&(height, dpi)).unwrap(); - let height_gap = (rect.height() - (4 * side + 5 * padding)) / big_height; + let height_gap = (rect.height() - rows_height.round() as u32) / big_height; rect.min.y += (height_gap * big_height) as i32; - let normal_side = (side + padding) as i32; - let (small_half_side, big_half_side) = halves(normal_side); - let (small_medium_length, big_medium_length) = halves(3 * normal_side); - let large_length = 2 * normal_side; - let huge_length = 2 * large_length; - - let remaining_width = rect.width() as i32 - 11 * normal_side; - let remaining_height = rect.height() as i32 - 4 * normal_side; - let (small_half_remaining_width, big_half_remaining_width) = halves(remaining_width); - let (small_half_remaining_height, big_half_remaining_height) = halves(remaining_height); - - // Row 1 - - for i in 0..10usize { - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + i as i32 * normal_side, - small_half_remaining_height); - self.children[i].resize(rect![min_pt, min_pt + normal_side], hub, context); - } - - // Row 2 - - let min_pt = rect.min + pt!(small_half_remaining_width, small_half_remaining_height + normal_side); - self.children[10].resize(rect![min_pt, min_pt + normal_side], hub, context); - - for i in 0..9usize { - let min_pt = rect.min + pt!(small_half_remaining_width + (i + 1) as i32 * normal_side, - small_half_remaining_height + normal_side); - self.children[11+i].resize(rect![min_pt, min_pt + normal_side], hub, context); - } - - let min_pt = rect.min + pt!(small_half_remaining_width + 10 * normal_side, small_half_remaining_height + normal_side); - self.children[20].resize(rect![min_pt, min_pt + normal_side], hub, context); - - // Row 3 - - let min_pt = rect.min + pt!(small_half_remaining_width, small_half_remaining_height + 2 * normal_side); - self.children[21].resize(rect![min_pt, min_pt + pt!(large_length, normal_side)], hub, context); + let start_y = rect.min.y as f32 + padding + (rect.height() as f32 - rows_height) / 2.0; + let mut index = 0; - for i in 0..7usize { - let min_pt = rect.min + pt!(small_half_remaining_width + (i + 2) as i32 * normal_side, - small_half_remaining_height + 2 * normal_side); - self.children[22+i].resize(rect![min_pt, min_pt + normal_side], hub, context); + for (i, row) in self.layout.keys.iter().enumerate() { + let y = start_y + i as f32 * (padding + key_height); + let row_width = (self.layout.widths[i].len() + 1) as f32 * padding + self.layout.widths[i].iter().sum::() * key_height; + let start_x = rect.min.x as f32 + padding + (rect.width() as f32 - row_width) / 2.0; + let mut dx = 0.0; + + for j in 0..row.len() { + let key_width = self.layout.widths[i][j] * key_height; + let x = start_x + dx; + dx += key_width + padding; + let key_rect = rect![x.round() as i32, + y.round() as i32, + (x + key_width).round() as i32, + (y + key_height).round() as i32]; + self.children[index].resize(key_rect, hub, context); + index += 1; + } } - let min_pt = rect.min + pt!(small_half_remaining_width + 9 * normal_side, small_half_remaining_height + 2 * normal_side); - self.children[29].resize(rect![min_pt, min_pt + pt!(large_length, normal_side)], hub, context); - - // Row 4 - - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side, small_half_remaining_height + 3 * normal_side); - self.children[30].resize(rect![min_pt, min_pt + pt!(small_medium_length, normal_side)], hub, context); - - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + small_medium_length, small_half_remaining_height + 3 * normal_side); - self.children[31].resize(rect![min_pt, min_pt + pt!(big_medium_length, normal_side)], hub, context); - - // Space bar - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + 3 * normal_side, small_half_remaining_height + 3 * normal_side); - self.children[32].resize(rect![min_pt, min_pt + pt!(huge_length, normal_side)], hub, context); - - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + 7 * normal_side, small_half_remaining_height + 3 * normal_side); - self.children[33].resize(rect![min_pt, min_pt + pt!(big_medium_length, normal_side)], hub, context); - - let min_pt = rect.min + pt!(small_half_remaining_width + small_half_side + 7 * normal_side + big_medium_length, small_half_remaining_height + 3 * normal_side); - self.children[34].resize(rect![min_pt, min_pt + pt!(small_medium_length, normal_side)], hub, context); - - // Boundary Fillers - self.children[35].resize(rect![rect.min, - pt!(rect.max.x - big_half_remaining_width, - rect.min.y + small_half_remaining_height)], - hub, context); - - self.children[36].resize(rect![pt!(rect.max.x - big_half_remaining_width, - rect.min.y), - pt!(rect.max.x, - rect.max.y - big_half_remaining_height)], - hub, context); - self.children[37].resize(rect![pt!(rect.min.x + small_half_remaining_width, - rect.max.y - big_half_remaining_height), - rect.max], - hub, context); - self.children[38].resize(rect![pt!(rect.min.x, - rect.min.y + small_half_remaining_height), - pt!(rect.min.x + small_half_remaining_width, - rect.max.y)], - hub, context); - - // In-between Fillers - let min_pt = pt!(rect.min.x + small_half_remaining_width, - rect.min.y + small_half_remaining_height); - self.children[39].resize(rect![min_pt, min_pt + pt!(small_half_side, - normal_side)], - hub, context); - - let min_pt = pt!(rect.min.x + small_half_remaining_width, - rect.max.y - big_half_remaining_height - normal_side); - self.children[40].resize(rect![min_pt, min_pt + pt!(small_half_side, - normal_side)], - hub, context); - - let min_pt = pt!(rect.max.x - big_half_remaining_width - big_half_side, - rect.min.y + small_half_remaining_height); - self.children[41].resize(rect![min_pt, min_pt + pt!(big_half_side, - normal_side)], - hub, context); - - let min_pt = pt!(rect.max.x - big_half_remaining_width - big_half_side, - rect.max.y - big_half_remaining_height - normal_side); - self.children[42].resize(rect![min_pt, min_pt + pt!(big_half_side, - normal_side)], - hub, context); - self.rect = rect; context.kb_rect = rect; } + fn is_background(&self) -> bool { + true + } + fn rect(&self) -> &Rectangle { &self.rect } @@ -489,32 +348,6 @@ impl View for Keyboard { } lazy_static! { - pub static ref DEFAULT_LAYOUT: Layout = Layout { - name: "US_en".to_string(), - outputs: [ - OutputKeys { - row1: ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], - row2: ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'], - row3: ['z', 'x', 'c', 'v', 'b', 'n', 'm'], - }, - OutputKeys { - row1: ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], - row2: ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'], - row3: ['Z', 'X', 'C', 'V', 'B', 'N', 'M'], - }, - OutputKeys { - row1: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'], - row2: ['\\', '=', ',', '\'', '_', '"', '.', '*', '/'], - row3: ['!', '-', '(', ':', ')', '+', '?'], - }, - OutputKeys { - row1: ['·', '“', '%', '^', '#', '$', '~', '×', '”', '°'], - row2: ['‘', '@', '[', '`', '|', '…', ']', '&', '’'], - row3: ['–', '<', '{', ';', '}', '>', '—'], - }, - ], - }; - // Most of the combination sequences come from X.org. // The chosen characters come from the layout described by // Robert Bringhurst in *The Elements of Typographic Style*, diff --git a/src/view/mod.rs b/src/view/mod.rs index fe03522..c17763b 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -321,6 +321,7 @@ pub enum ViewId { ClockMenu, SearchTargetMenu, InputHistoryMenu, + KeyboardLayoutMenu, Frontlight, Dictionary, FontSizeMenu, @@ -491,6 +492,7 @@ pub enum EntryId { SetRotationLock(Option), SetSearchTarget(Option), SetInputText(ViewId, String), + SetKeyboardLayout(String), ToggleFuzzy, ToggleInverted, ToggleMonochrome, diff --git a/src/view/reader/mod.rs b/src/view/reader/mod.rs index a4f7815..4be64d2 100644 --- a/src/view/reader/mod.rs +++ b/src/view/reader/mod.rs @@ -34,7 +34,7 @@ use crate::view::common::{toggle_main_menu, toggle_battery_menu, toggle_clock_me use crate::view::filler::Filler; use crate::view::named_input::NamedInput; use crate::view::search_bar::SearchBar; -use crate::view::keyboard::{Keyboard, DEFAULT_LAYOUT}; +use crate::view::keyboard::Keyboard; use crate::view::menu::{Menu, MenuKind}; use crate::view::notification::Notification; use crate::settings::{guess_frontlight, FinishedAction}; @@ -1153,7 +1153,7 @@ impl Reader { self.children.insert(index, Box::new(separator) as Box); } - let keyboard = Keyboard::new(&mut kb_rect, DEFAULT_LAYOUT.clone(), number, context); + let keyboard = Keyboard::new(&mut kb_rect, number, context); self.children.insert(index, Box::new(keyboard) as Box); let separator = Filler::new(rect![self.rect.min.x, kb_rect.min.y - thickness, -- 2.51.2