From 3f78095fe3f3877360e0358c14e6189bc6a1c3ee Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 27 May 2025 13:08:32 +0300 Subject: [PATCH] ui: keyboard language switch --- locales/de.yml | 47 +++++- locales/en.yml | 47 +++++- locales/fr.yml | 47 +++++- locales/ru.yml | 47 +++++- locales/tr.yml | 47 +++++- locales/zh-CN.yml | 47 +++++- src/gui/app.rs | 8 +- src/gui/views/input/edit.rs | 2 +- src/gui/views/input/keyboard.rs | 252 +++++++++++++++++--------------- 9 files changed, 417 insertions(+), 127 deletions(-) diff --git a/locales/de.yml b/locales/de.yml index c5d91dbb..3e2d0bce 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -291,4 +291,49 @@ modal: add: Hinzufügen modal_exit: description: Sind Sie sicher, dass Sie die Anwendung beenden wollen? - exit: Schließen \ No newline at end of file + exit: Schließen +keyboard: + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 0: 0 + 01: '-' + q: q + w: w + e: e + r: r + t: t + y: y + u: u + i: i + o: o + p: p + p1: '"' + a: a + s: s + d: d + f: f + g: g + h: h + j: j + k: k + l: l + l1: \ + l2: '?' + z1: ',' + z: z + x: x + c: c + v: v + b: b + n: n + m: m + m1: . + m2: ':' + m3: / \ No newline at end of file diff --git a/locales/en.yml b/locales/en.yml index 7c294722..04e31beb 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -291,4 +291,49 @@ modal: add: Add modal_exit: description: Are you sure you want to quit the application? - exit: Exit \ No newline at end of file + exit: Exit +keyboard: + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 0: 0 + 01: '-' + q: q + w: w + e: e + r: r + t: t + y: y + u: u + i: i + o: o + p: p + p1: '"' + a: a + s: s + d: d + f: f + g: g + h: h + j: j + k: k + l: l + l1: \ + l2: '?' + z1: ',' + z: z + x: x + c: c + v: v + b: b + n: n + m: m + m1: . + m2: ':' + m3: / \ No newline at end of file diff --git a/locales/fr.yml b/locales/fr.yml index a546deb8..f19a34cd 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -291,4 +291,49 @@ modal: add: Ajouter modal_exit: description: "Êtes-vous sûr de vouloir quitter l'application ?" - exit: Quitter \ No newline at end of file + exit: Quitter +keyboard: + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 0: 0 + 01: '-' + q: q + w: w + e: e + r: r + t: t + y: y + u: u + i: i + o: o + p: p + p1: '"' + a: a + s: s + d: d + f: f + g: g + h: h + j: j + k: k + l: l + l1: \ + l2: '?' + z1: ',' + z: z + x: x + c: c + v: v + b: b + n: n + m: m + m1: . + m2: ':' + m3: / \ No newline at end of file diff --git a/locales/ru.yml b/locales/ru.yml index 04611deb..7bda8667 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -291,4 +291,49 @@ modal: add: Добавить modal_exit: description: Вы уверены, что хотите выйти из приложения? - exit: Выход \ No newline at end of file + exit: Выход +keyboard: + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 0: 0 + 01: '-' + q: й + w: ц + e: у + r: к + t: е + y: н + u: г + i: ш + o: щ + p: з + p1: х + a: ф + s: ы + d: в + f: а + g: п + h: р + j: о + k: л + l: д + l1: ж + l2: э + z1: я + z: ч + x: с + c: м + v: и + b: т + n: ь + m: б + m1: ю + m2: ё + m3: ъ \ No newline at end of file diff --git a/locales/tr.yml b/locales/tr.yml index d7582a51..610a069c 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -291,4 +291,49 @@ modal: add: Ekle modal_exit: description: Uygulamadan cikmak için exit, emin misiniz? - exit: Exit \ No newline at end of file + exit: Exit +keyboard: + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 0: 0 + 01: '-' + q: q + w: w + e: e + r: r + t: t + y: y + u: u + i: i + o: o + p: p + p1: '"' + a: a + s: s + d: d + f: f + g: g + h: h + j: j + k: k + l: l + l1: \ + l2: '?' + z1: ',' + z: z + x: x + c: c + v: v + b: b + n: n + m: m + m1: . + m2: ':' + m3: / \ No newline at end of file diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index ea262b23..cff543ce 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -291,4 +291,49 @@ modal: add: 添加 modal_exit: description: 您确定要退出应用程序吗? - exit: 退出 \ No newline at end of file + exit: 退出手 +keyboard: + 1: 1 + 2: 2 + 3: 3 + 4: 4 + 5: 5 + 6: 6 + 7: 7 + 8: 8 + 9: 9 + 0: 0 + 01: '-' + q: 手 + w: 田 + e: 水 + r: 口 + t: 廿 + y: 卜 + u: 山 + i: 戈 + o: 人 + p: 心 + p1: '"' + a: 日 + s: 尸 + d: 木 + f: 火 + g: 土 + h: 竹 + j: 十 + k: 大 + l: 中 + l1: \ + l2: '?' + z1: ',' + z: 重 + x: 難 + c: 金 + v: 女 + b: 月 + n: 弓 + m: 一 + m1: . + m2: ':' + m3: / \ No newline at end of file diff --git a/src/gui/app.rs b/src/gui/app.rs index 26cf4def..373ae41e 100755 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -78,12 +78,8 @@ impl App { // Handle Esc keyboard key event and platform Back button key event. let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed); if back_pressed || ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) { - // Hide keyboard or pass event to content. - if KeyboardContent::showing() { - KeyboardContent::hide(); - } else { - self.content.on_back(&self.platform); - } + // Pass event to content. + self.content.on_back(&self.platform); if back_pressed { BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed); } diff --git a/src/gui/views/input/edit.rs b/src/gui/views/input/edit.rs index d689e1c1..18d2f034 100644 --- a/src/gui/views/input/edit.rs +++ b/src/gui/views/input/edit.rs @@ -162,7 +162,7 @@ impl TextEdit { if self.enter_pressed { KeyboardContent::unshift(); } - KeyboardContent::show(self.numeric); + KeyboardContent::show_window(self.numeric); } }); }); diff --git a/src/gui/views/input/keyboard.rs b/src/gui/views/input/keyboard.rs index 988b1784..8ee47b91 100644 --- a/src/gui/views/input/keyboard.rs +++ b/src/gui/views/input/keyboard.rs @@ -12,23 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; use eframe::emath::Align; use eframe::epaint::{Margin, Shadow}; -use egui::{Align2, Button, Color32, CursorIcon, Layout, Rect, Response, RichText, Stroke, Vec2, Widget}; -use parking_lot::RwLock; +use egui::{Align2, Button, Color32, CursorIcon, Layout, Rect, Response, RichText, Vec2, Widget}; use lazy_static::lazy_static; -use crate::AppConfig; -use crate::gui::Colors; -use crate::gui::icons::{ARROW_FAT_UP, BACKSPACE, KEY_RETURN, TRANSLATE}; +use parking_lot::RwLock; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use crate::gui::icons::{ARROW_FAT_UP, BACKSPACE, GLOBE_SIMPLE, KEY_RETURN}; use crate::gui::views::{KeyboardEvent, KeyboardLayout, View}; +use crate::gui::Colors; +use crate::AppConfig; lazy_static! { /// Last input event. static ref LAST_EVENT: Arc>> = Arc::new(RwLock::new(None)); - /// Flag to show keyboard. - static ref SHOW_KEYBOARD: AtomicBool = AtomicBool::new(false); + /// Flag to show keyboard [`egui::Window`]. + static ref SHOW_WINDOW: AtomicBool = AtomicBool::new(false); /// Flag to enable text shifting. static ref UPPERCASE: AtomicBool = AtomicBool::new(false); /// Flag to show numeric layout. @@ -52,12 +53,10 @@ impl Default for KeyboardContent { impl KeyboardContent { /// Maximum keyboard content width. const MAX_WIDTH: f32 = 600.0; - /// Buttons content margin. - const MARGIN: f32 = 5.0; /// Draw keyboard content as separate [`Window`]. pub fn window_ui(&mut self, ctx: &egui::Context) { - if !KeyboardContent::showing() { + if !KeyboardContent::window_showing() { return; } let width = ctx.screen_rect().width(); @@ -76,10 +75,10 @@ impl KeyboardContent { color: Color32::from_black_alpha(32), }, inner_margin: Margin { - left: View::get_left_inset() + Self::MARGIN, - right: View::get_right_inset() + Self::MARGIN, - top: Self::MARGIN, - bottom: View::get_bottom_inset() + Self::MARGIN, + left: View::get_left_inset(), + right: View::get_right_inset(), + top: 1.0, + bottom: View::get_bottom_inset() + 1.0, }, fill: Colors::fill(), ..Default::default() @@ -88,7 +87,7 @@ impl KeyboardContent { ui.set_min_width(width); // Calculate content width. let side_insets = View::get_left_inset() + View::get_right_inset(); - let available_width = width - (side_insets + Self::MARGIN * 2.0); + let available_width = width - side_insets; let w = f32::min(available_width, Self::MAX_WIDTH); View::max_width_ui(ui, w, |ui| { self.ui(ui); @@ -102,15 +101,15 @@ impl KeyboardContent { /// Draw keyboard content. pub fn ui(&mut self, ui: &mut egui::Ui) { // Setup spacing between buttons. - ui.style_mut().spacing.item_spacing = egui::vec2(Self::MARGIN, 0.0); + ui.style_mut().spacing.item_spacing = egui::vec2(0.0, 0.0); // Setup vertical padding inside buttons. - ui.style_mut().spacing.button_padding = egui::vec2(Self::MARGIN, 8.0); + ui.style_mut().spacing.button_padding = egui::vec2(0.0, 8.0); let button_rect = match self.mode { KeyboardLayout::TEXT => Self::text_ui(ui), KeyboardLayout::SYMBOLS => Self::symbols_ui(ui), }; - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); // Draw bottom keyboard buttons. let bottom_size = { @@ -118,42 +117,82 @@ impl KeyboardContent { r.set_width(ui.available_width()); r.size() }; - let button_width = button_rect.width(); - ui.allocate_ui_with_layout(bottom_size, Layout::right_to_left(Align::Max), |ui| { + let button_width = ui.available_width() / if self.mode != KeyboardLayout::SYMBOLS { + 11.0 + } else { + 10.0 + }; + ui.allocate_ui_with_layout(bottom_size, Layout::right_to_left(Align::Center), |ui| { + // Enter key input. ui.horizontal_centered(|ui| { - ui.set_max_width(button_width * 2.0 + Self::MARGIN); - Self::button_ui(KEY_RETURN, - Colors::white_or_black(false), - Some(Colors::green()), - ui, - |_| { - Self::input_event(KeyboardEvent::ENTER); - }); + ui.set_max_width(button_width * 2.0 + 1.0); + Self::custom_button_ui(KEY_RETURN.to_string(), + Colors::white_or_black(false), + Some(Colors::green()), + ui, + |_| { + Self::input_event(KeyboardEvent::ENTER); + }); }); + // Backspace key input. ui.horizontal_centered(|ui| { - ui.set_max_width(button_width * 5.0 + 4.0 * Self::MARGIN); - Self::button_ui(" ", Colors::inactive_text(), None, ui, |l| { + ui.set_max_width(button_width * 1.0); + Self::custom_button_ui(BACKSPACE.to_string(), + Colors::red(), + Some(Colors::fill_lite()), + ui, + |_| { + Self::input_event(KeyboardEvent::CLEAR); + }); + }); + // Space key input. + ui.horizontal_centered(|ui| { + ui.set_max_width(button_width * 4.0); + Self::custom_button_ui(" ".to_string(), Colors::inactive_text(), None, ui, |l| { Self::input_event(KeyboardEvent::TEXT(l)); }); }); - ui.horizontal_centered(|ui| { - ui.set_max_width(button_width); - Self::button_ui(TRANSLATE, - Colors::text_button(), - Some(Colors::fill_lite()), - ui, - |_| { - AppConfig::toggle_english_keyboard() - }); - }); + if self.mode != KeyboardLayout::SYMBOLS { + // Switch to english and back. + ui.horizontal_centered(|ui| { + ui.set_max_width(button_width); + Self::custom_button_ui(GLOBE_SIMPLE.to_string(), + Colors::text_button(), + Some(Colors::fill_lite()), + ui, + |_| { + AppConfig::toggle_english_keyboard() + }); + }); + // Shift key input. + ui.horizontal_centered(|ui| { + ui.set_max_width(button_width); + let uppercase = UPPERCASE.load(Ordering::Relaxed); + let color = if uppercase { + Colors::yellow_dark() + } else { + Colors::inactive_text() + }; + Self::custom_button_ui(ARROW_FAT_UP.to_string(), + color, + Some(Colors::fill_lite()), + ui, |_| { + UPPERCASE.store(!uppercase, Ordering::Relaxed); + }); + }); + } + // Switch to symbols and back. ui.horizontal_centered(|ui| { let label = if self.mode == KeyboardLayout::SYMBOLS { - "ABC" + let q = t!("keyboard.q", locale = Self::locale().as_str()); + let w = t!("keyboard.w", locale = Self::locale().as_str()); + let e = t!("keyboard.e", locale = Self::locale().as_str()); + format!("{}{}{}", q, w, e).to_uppercase() } else { - "?/ツ" + "?/ツ".to_string() }; let mut mode = self.mode.clone(); - Self::button_ui(label, Colors::text_button(), Some(Colors::fill_lite()), ui, |_| { + Self::custom_button_ui(label, Colors::text(false), Some(Colors::fill_lite()), ui, |_| { if self.mode == KeyboardLayout::SYMBOLS { mode = KeyboardLayout::TEXT; } else { @@ -168,58 +207,37 @@ impl KeyboardContent { /// Draw text content returning button [`Rect`]. fn text_ui(ui: &mut egui::Ui) -> Rect { let mut button_rect = ui.available_rect_before_wrap(); - let tl_0: Vec<&str> = vec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]; + let tl_0: Vec<&str> = vec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "01"]; ui.columns(tl_0.len(), |columns| { for (index, s) in tl_0.iter().enumerate() { - button_rect = Self::input_button_ui(s, &mut columns[index]); + button_rect = Self::input_button_ui(s, true, &mut columns[index]); } }); - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); - let tl_1: Vec<&str> = vec!["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]; + let tl_1: Vec<&str> = vec!["q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "p1"]; ui.columns(tl_1.len(), |columns| { for (index, s) in tl_1.iter().enumerate() { - Self::input_button_ui(s, &mut columns[index]); + Self::input_button_ui(s, true, &mut columns[index]); } }); - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); - let tl_2: Vec<&str> = vec!["a", "s", "d", "f", "g", "h", "j", "k", "l", ":"]; + let tl_2: Vec<&str> = vec!["a", "s", "d", "f", "g", "h", "j", "k", "l", "l1", "l2"]; ui.columns(tl_2.len(), |columns| { for (index, s) in tl_2.iter().enumerate() { - Self::input_button_ui(s, &mut columns[index]); + Self::input_button_ui(s, true, &mut columns[index]); } }); - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); - let tl_3: Vec<&str> = vec![ARROW_FAT_UP, "z", "x", "c", "v", "b", "n", "m", ".", BACKSPACE]; + let tl_3: Vec<&str> = vec!["z1", "z", "x", "c", "v", "b", "n", "m", "m1", "m2", "m3"]; ui.columns(tl_3.len(), |columns| { for (index, s) in tl_3.iter().enumerate() { - if index == 0 { - // Check for shift input. - let uppercase = UPPERCASE.load(Ordering::Relaxed); - let color = if uppercase { - Colors::yellow_dark() - } else { - Colors::inactive_text() - }; - Self::button_ui(s, color, Some(Colors::fill_lite()), &mut columns[index], |_| { - UPPERCASE.store(!uppercase, Ordering::Relaxed); - }); - } else if index == tl_3.len() - 1 { - // Check for backspace input. - Self::button_ui(s, - Colors::red(), - Some(Colors::fill_lite()), - &mut columns[index], - |_| { - Self::input_event(KeyboardEvent::CLEAR); - }); - } else { - Self::input_button_ui(s, &mut columns[index]); - } + Self::input_button_ui(s, true, &mut columns[index]); } }); + button_rect } @@ -229,53 +247,43 @@ impl KeyboardContent { let tl_0: Vec<&str> = vec!["[", "]", "{", "}", "#", "%", "^", "*", "+", "="]; ui.columns(tl_0.len(), |columns| { for (index, s) in tl_0.iter().enumerate() { - button_rect = Self::input_button_ui(s, &mut columns[index]); + button_rect = Self::input_button_ui(s, false, &mut columns[index]); } }); - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); - let tl_1: Vec<&str> = vec!["_", "\\", "|", "~", "<", ">", "`", "√", "π", "•"]; + let tl_1: Vec<&str> = vec!["_", "\\", "|", "~", "<", ">", "№", "√", "π", "•"]; ui.columns(tl_1.len(), |columns| { for (index, s) in tl_1.iter().enumerate() { - Self::input_button_ui(s, &mut columns[index]); + Self::input_button_ui(s, false, &mut columns[index]); } }); - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); - let tl_2: Vec<&str> = vec!["-", "/", ":", ";", "(", ")", "$", "&", "@", "\""]; + let tl_2: Vec<&str> = vec!["-", "/", ":", ";", "(", ")", "`", "&", "@", "\""]; ui.columns(tl_2.len(), |columns| { for (index, s) in tl_2.iter().enumerate() { - Self::input_button_ui(s, &mut columns[index]); + Self::input_button_ui(s, false, &mut columns[index]); } }); - ui.add_space(View::TAB_ITEMS_PADDING); + ui.add_space(2.0); - let tl_3: Vec<&str> = vec![".", ",", "?", "!", "€", "£", "¥", "¢", "ツ", BACKSPACE]; + let tl_3: Vec<&str> = vec![".", ",", "?", "!", "€", "£", "¥", "$", "¢", "ツ"]; ui.columns(tl_3.len(), |columns| { for (index, s) in tl_3.iter().enumerate() { - if index == tl_3.len() - 1 { - // Check for backspace input. - Self::button_ui(s, - Colors::red(), - Some(Colors::fill_lite()), - &mut columns[index], |_| { - Self::input_event(KeyboardEvent::CLEAR); - }); - } else { - Self::input_button_ui(s, &mut columns[index]); - } + Self::input_button_ui(s, false, &mut columns[index]); } }); button_rect } - /// Draw keyboard button. - fn button_ui(s: &str, - color: Color32, - bg: Option, - ui: &mut egui::Ui, - mut cb: impl FnMut(String)) -> Response { + /// Draw custom keyboard button. + fn custom_button_ui(s: String, + color: Color32, + bg: Option, + ui: &mut egui::Ui, + mut cb: impl FnMut(String)) -> Response { ui.vertical_centered_justified(|ui| { // Disable expansion on click/hover. ui.style_mut().visuals.widgets.hovered.expansion = 0.0; @@ -286,15 +294,16 @@ impl KeyboardContent { ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill(); // Setup stroke colors. ui.visuals_mut().widgets.inactive.bg_stroke = View::item_stroke(); - ui.visuals_mut().widgets.hovered.bg_stroke = View::hover_stroke(); - ui.visuals_mut().widgets.active.bg_stroke = Stroke::NONE; + ui.visuals_mut().widgets.hovered.bg_stroke = View::item_stroke(); + ui.visuals_mut().widgets.active.bg_stroke = View::hover_stroke(); let label = if UPPERCASE.load(Ordering::Relaxed) { s.to_uppercase() } else { s.to_string() }; - let mut button = Button::new(RichText::new(label.clone()).size(17.0).color(color)); + let mut button = Button::new(RichText::new(label.clone()).size(17.0).color(color)) + .rounding(egui::Rounding::ZERO); if let Some(bg) = bg { button = button.fill(bg); } @@ -306,14 +315,29 @@ impl KeyboardContent { } /// Draw input button. - fn input_button_ui(s: &str, ui: &mut egui::Ui) -> Rect { - let rect = Self::button_ui(s, Colors::text_button(), None, ui, |l| { + fn input_button_ui(s: &str, translate: bool, ui: &mut egui::Ui) -> Rect { + let value = if translate { + t!(format!("keyboard.{}", s).as_str(), locale = Self::locale().as_str()) + } else { + s.to_string() + }; + let rect = Self::custom_button_ui(value, Colors::text_button(), None, ui, |l| { Self::input_event(KeyboardEvent::TEXT(l)); UPPERCASE.store(false, Ordering::Relaxed); }).rect; rect } + /// Get input locale. + fn locale() -> String { + let english = AppConfig::english_keyboard(); + if english { + "en".to_string() + } else { + AppConfig::locale().unwrap_or("en".to_string()) + } + } + /// Save keyboard event to consume later. fn input_event(event: KeyboardEvent) { let mut w_input = LAST_EVENT.write(); @@ -336,14 +360,14 @@ impl KeyboardContent { } /// Check if keyboard is showing. - pub fn showing() -> bool { - SHOW_KEYBOARD.load(Ordering::Relaxed) + pub fn window_showing() -> bool { + SHOW_WINDOW.load(Ordering::Relaxed) } /// Show keyboard. - pub fn show(numeric: bool) { + pub fn show_window(numeric: bool) { NUMERIC.store(numeric, Ordering::Relaxed); - SHOW_KEYBOARD.store(true, Ordering::Relaxed); + SHOW_WINDOW.store(true, Ordering::Relaxed); } /// Emulate Shift key pressing. @@ -358,6 +382,6 @@ impl KeyboardContent { /// Hide keyboard. pub fn hide() { - SHOW_KEYBOARD.store(false, Ordering::Relaxed); + SHOW_WINDOW.store(false, Ordering::Relaxed); } } \ No newline at end of file