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,