diff --git a/src/gui/app.rs b/src/gui/app.rs index e527c029..26cf4def 100755 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -139,7 +139,7 @@ impl App { } // Show soft keyboard content. - self.keyboard_content.ui(ctx); + self.keyboard_content.window_ui(ctx); // Provide incoming data to wallets. if let Some(data) = crate::consume_incoming_data() { diff --git a/src/gui/views/input/edit.rs b/src/gui/views/input/edit.rs new file mode 100644 index 00000000..ba14b365 --- /dev/null +++ b/src/gui/views/input/edit.rs @@ -0,0 +1,307 @@ +// Copyright 2025 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use eframe::emath::Align; +use egui::{Layout, TextBuffer, TextStyle, Widget}; +use egui::text_edit::TextEditState; +use crate::gui::Colors; +use crate::gui::icons::{CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN}; +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::input::keyboard::KeyboardContent; +use crate::gui::views::{KeyboardEvent, View}; + +/// Text input content. +pub struct TextEdit { + /// View identifier. + pub id: egui::Id, + /// Check if horizontal centering is needed. + pub h_center: bool, + /// Check if focus is needed. + pub focus: bool, + /// Check if focus request was passed. + focus_request: bool, + /// Hide letters and draw button to show/hide letters. + pub password: bool, + /// Show copy button. + pub copy: bool, + /// Show paste button. + pub paste: bool, + /// Show button to scan QR code into text. + pub scan_qr: bool, + /// Callback when scan button was pressed. + pub scan_pressed: bool, + /// Callback when Enter key was pressed. + pub enter_pressed: bool, + /// Flag to enter only numbers. + pub numeric: bool, +} + +impl TextEdit { + /// Default height of [`egui::TextEdit`] view. + const TEXT_EDIT_HEIGHT: f32 = 37.0; + + pub fn new(id: egui::Id) -> Self { + Self { + id, + h_center: false, + focus: true, + focus_request: false, + password: false, + copy: false, + paste: false, + scan_qr: false, + scan_pressed: false, + enter_pressed: false, + numeric: false, + } + } + + /// Draw text input content. + pub fn ui(&mut self, ui: &mut egui::Ui, input: &mut String, cb: &dyn PlatformCallbacks) { + let mut layout_rect = ui.available_rect_before_wrap(); + layout_rect.set_height(Self::TEXT_EDIT_HEIGHT); + ui.allocate_ui_with_layout(layout_rect.size(), Layout::right_to_left(Align::Center), |ui| { + let mut hide_input = false; + if self.password { + let show_pass_id = egui::Id::new(self.id).with("_show_pass"); + hide_input = ui.data(|data| { + data.get_temp(show_pass_id) + }).unwrap_or(true); + // Draw button to show/hide current password. + let eye_icon = if hide_input { EYE } else { EYE_SLASH }; + View::button_ui(ui, eye_icon.to_string(), Colors::white_or_black(false), |ui| { + hide_input = !hide_input; + ui.data_mut(|data| { + data.insert_temp(show_pass_id, hide_input); + }); + }); + ui.add_space(8.0); + } + + // Setup copy button. + if self.copy { + let copy_icon = COPY.to_string(); + View::button(ui, copy_icon, Colors::white_or_black(false), || { + cb.copy_string_to_buffer(input.clone()); + }); + ui.add_space(8.0); + } + + // Setup paste button. + if self.paste { + let paste_icon = CLIPBOARD_TEXT.to_string(); + View::button(ui, paste_icon, Colors::white_or_black(false), || { + *input = cb.get_string_from_buffer(); + }); + ui.add_space(8.0); + } + + // Setup scan QR code button. + if self.scan_qr { + let scan_icon = SCAN.to_string(); + View::button(ui, scan_icon, Colors::white_or_black(false), || { + cb.start_camera(); + self.scan_pressed = true; + }); + ui.add_space(8.0); + } + + let layout_size = ui.available_size(); + ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { + // Setup text edit size. + let mut edit_rect = ui.available_rect_before_wrap(); + edit_rect.set_height(Self::TEXT_EDIT_HEIGHT); + + // Setup focused input value. + let focused_input_id = egui::Id::new("focused_input_id"); + let focused = ui.data(|data| { + data.get_temp(focused_input_id) + }).unwrap_or(egui::Id::new("")) == self.id; + + // Show text edit. + let text_edit_resp = egui::TextEdit::singleline(input) + .id(self.id) + .margin(egui::Vec2::new(2.0, 0.0)) + .font(TextStyle::Heading) + .min_size(edit_rect.size()) + .horizontal_align(if self.h_center { Align::Center } else { Align::Min }) + .vertical_align(Align::Center) + .password(hide_input) + .cursor_at_end(true) + .ui(ui); + + // Setup focus state. + let clicked = text_edit_resp.clicked(); + if !text_edit_resp.has_focus() && + (self.focus || self.focus_request || clicked || focused) { + text_edit_resp.request_focus(); + } + + // Apply text from software input. + if text_edit_resp.has_focus() { + ui.data_mut(|data| { + data.insert_temp(focused_input_id, self.id); + }); + self.enter_pressed = self.on_soft_input(ui, self.id, false, input); + // Check Enter key input. + if !self.focus_request { + if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter)) { + self.enter_pressed = true; + } + } + if self.enter_pressed { + KeyboardContent::unshift(); + } + KeyboardContent::show(self.numeric); + } + }); + }); + } + + /// Apply soft keyboard input data to provided String, returns `true` if Enter was pressed. + fn on_soft_input(&self, ui: &mut egui::Ui, id: egui::Id, multiline: bool, value: &mut String) + -> bool { + if let Some(input) = KeyboardContent::consume_event() { + let mut enter_pressed = false; + let mut state = TextEditState::load(ui.ctx(), id).unwrap(); + match state.cursor.char_range() { + None => {} + Some(range) => { + let mut r = range.clone(); + let mut index = r.primary.index; + + let selected = r.primary.index != r.secondary.index; + let start_select = f32::min(r.primary.index as f32, + r.secondary.index as f32) as usize; + let end_select = f32::max(r.primary.index as f32, + r.secondary.index as f32) as usize; + match input { + KeyboardEvent::TEXT(text) => { + if selected { + *value = { + let part1: String = value.chars() + .skip(0) + .take(start_select) + .collect(); + let part2: String = value.chars() + .skip(end_select) + .take(value.len() - end_select) + .collect(); + format!("{}{}{}", part1, text, part2) + }; + index = start_select + 1; + } else { + value.insert_text(text.as_str(), index); + index = index + 1; + } + } + KeyboardEvent::CLEAR => { + if selected { + *value = { + let part1: String = value.chars() + .skip(0) + .take(start_select) + .collect(); + let part2: String = value.chars() + .skip(end_select) + .take(value.len() - end_select) + .collect(); + format!("{}{}", part1, part2) + }; + index = start_select; + } else if index != 0 { + *value = { + let part1: String = value.chars() + .skip(0) + .take(index - 1) + .collect(); + let part2: String = value.chars() + .skip(index) + .take(value.len() - index) + .collect(); + format!("{}{}", part1, part2) + }; + index = index - 1; + } + } + KeyboardEvent::ENTER => { + if multiline { + value.insert_text("\n", index); + index = index + 1; + } else { + enter_pressed = true; + } + } + } + // Setup cursor index. + r.primary.index = index; + r.secondary.index = r.primary.index; + + state.cursor.set_char_range(Some(r)); + TextEditState::store(state, ui.ctx(), id); + } + } + return enter_pressed; + } + false + } + + /// Center text horizontally. + pub fn h_center(mut self) -> Self { + self.h_center = true; + self + } + + /// Enable or disable constant focus. + pub fn focus(mut self, focus: bool) -> Self { + self.focus = focus; + self + } + + /// Focus on field. + pub fn focus_request(&mut self) { + self.focus_request = true; + } + + /// Allow input of numbers only. + pub fn numeric(mut self) -> Self { + self.numeric = true; + self + } + + /// Hide letters and draw button to show/hide letters. + pub fn password(mut self) -> Self { + self.password = true; + self + } + + /// Show button to copy text. + pub fn copy(mut self) -> Self { + self.copy = true; + self + } + + /// Show button to paste text. + pub fn paste(mut self) -> Self { + self.paste = true; + self + } + + /// Show button to scan QR code to text. + pub fn scan_qr(mut self) -> Self { + self.scan_qr = true; + self.scan_pressed = false; + self + } +} \ No newline at end of file diff --git a/src/gui/views/keyboard.rs b/src/gui/views/input/keyboard.rs similarity index 58% rename from src/gui/views/keyboard.rs rename to src/gui/views/input/keyboard.rs index 805a63d0..988b1784 100644 --- a/src/gui/views/keyboard.rs +++ b/src/gui/views/input/keyboard.rs @@ -19,39 +19,32 @@ use eframe::epaint::{Margin, Shadow}; use egui::{Align2, Button, Color32, CursorIcon, Layout, Rect, Response, RichText, Stroke, Vec2, Widget}; use parking_lot::RwLock; use lazy_static::lazy_static; - +use crate::AppConfig; use crate::gui::Colors; use crate::gui::icons::{ARROW_FAT_UP, BACKSPACE, KEY_RETURN, TRANSLATE}; -use crate::gui::views::View; +use crate::gui::views::{KeyboardEvent, KeyboardLayout, View}; lazy_static! { - /// Last input from software keyboard. - static ref LAST_INPUT: Arc>> = Arc::new(RwLock::new(None)); - /// Flag to show software keyboard. + /// 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 English layout at keyboard. - static ref ENG_KEYBOARD: AtomicBool = AtomicBool::new(false); -} - -/// Software keyboard input. -#[derive(Clone)] -pub enum KeyboardInput { - TEXT(String), CLEAR, ENTER + /// Flag to enable text shifting. + static ref UPPERCASE: AtomicBool = AtomicBool::new(false); + /// Flag to show numeric layout. + static ref NUMERIC: AtomicBool = AtomicBool::new(false); } /// Software keyboard content. pub struct KeyboardContent { - /// Flag to check if uppercase text entering is enabled. - uppercase: bool, - /// Flag to show symbols layout. - symbols: bool, + /// Keyboard layout. + mode: KeyboardLayout } impl Default for KeyboardContent { fn default() -> Self { Self { - uppercase: false, - symbols: false, + mode: KeyboardLayout::TEXT, } } } @@ -62,8 +55,8 @@ impl KeyboardContent { /// Buttons content margin. const MARGIN: f32 = 5.0; - /// Draw keyboard content. - pub fn ui(&mut self, ctx: &egui::Context) { + /// Draw keyboard content as separate [`Window`]. + pub fn window_ui(&mut self, ctx: &egui::Context) { if !KeyboardContent::showing() { return; } @@ -80,7 +73,7 @@ impl KeyboardContent { offset: Default::default(), blur: 30.0, spread: 3.0, - color: egui::Color32::from_black_alpha(32), + color: Color32::from_black_alpha(32), }, inner_margin: Margin { left: View::get_left_inset() + Self::MARGIN, @@ -98,66 +91,7 @@ impl KeyboardContent { let available_width = width - (side_insets + Self::MARGIN * 2.0); let w = f32::min(available_width, Self::MAX_WIDTH); View::max_width_ui(ui, w, |ui| { - // Setup spacing between buttons. - ui.style_mut().spacing.item_spacing = egui::vec2(Self::MARGIN, 0.0); - // Setup vertical padding inside buttons. - ui.style_mut().spacing.button_padding = egui::vec2(Self::MARGIN, 8.0); - - let button_rect = if self.symbols { - self.symbols_ui(ui) - } else { - self.text_ui(ui) - }; - ui.add_space(View::TAB_ITEMS_PADDING); - - // Draw bottom keyboard buttons. - let bottom_size = { - let mut r = button_rect.clone(); - 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| { - 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::on_input(KeyboardInput::ENTER); - }); - }); - 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| { - Self::on_input(KeyboardInput::TEXT(l)); - }); - }); - ui.horizontal_centered(|ui| { - ui.set_max_width(button_width); - self.button_ui(TRANSLATE, - Colors::text_button(), - Some(Colors::fill_lite()), - ui, - |_| { - let eng_keyboard = ENG_KEYBOARD.load(Ordering::Relaxed); - ENG_KEYBOARD.store(!eng_keyboard, Ordering::Relaxed); - }); - }); - ui.horizontal_centered(|ui| { - let label = if self.symbols { - "ABC" - } else { - "?/ツ" - }; - let mut symbols = self.symbols; - self.button_ui(label, Colors::text_button(), Some(Colors::fill_lite()), ui, |_| { - symbols = !self.symbols; - }); - self.symbols = symbols; - }); - }); + self.ui(ui); }); }).unwrap().response.layer_id; @@ -165,13 +99,79 @@ impl KeyboardContent { ctx.move_to_top(layer_id); } + /// 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); + // Setup vertical padding inside buttons. + ui.style_mut().spacing.button_padding = egui::vec2(Self::MARGIN, 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); + + // Draw bottom keyboard buttons. + let bottom_size = { + let mut r = button_rect.clone(); + 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| { + 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.horizontal_centered(|ui| { + ui.set_max_width(button_width * 5.0 + 4.0 * Self::MARGIN); + Self::button_ui(" ", 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() + }); + }); + ui.horizontal_centered(|ui| { + let label = if self.mode == KeyboardLayout::SYMBOLS { + "ABC" + } else { + "?/ツ" + }; + let mut mode = self.mode.clone(); + Self::button_ui(label, Colors::text_button(), Some(Colors::fill_lite()), ui, |_| { + if self.mode == KeyboardLayout::SYMBOLS { + mode = KeyboardLayout::TEXT; + } else { + mode = KeyboardLayout::SYMBOLS; + } + }); + self.mode = mode; + }); + }); + } + /// Draw text content returning button [`Rect`]. - fn text_ui(&mut self, ui: &mut egui::Ui) -> 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"]; 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, &mut columns[index]); } }); ui.add_space(View::TAB_ITEMS_PADDING); @@ -179,7 +179,7 @@ impl KeyboardContent { let tl_1: Vec<&str> = vec!["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]; 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, &mut columns[index]); } }); ui.add_space(View::TAB_ITEMS_PADDING); @@ -187,7 +187,7 @@ impl KeyboardContent { let tl_2: Vec<&str> = vec!["a", "s", "d", "f", "g", "h", "j", "k", "l", ":"]; 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, &mut columns[index]); } }); ui.add_space(View::TAB_ITEMS_PADDING); @@ -197,27 +197,26 @@ impl KeyboardContent { for (index, s) in tl_3.iter().enumerate() { if index == 0 { // Check for shift input. - let color = if self.uppercase { + let uppercase = UPPERCASE.load(Ordering::Relaxed); + let color = if uppercase { Colors::yellow_dark() } else { Colors::inactive_text() }; - let mut uppercase = self.uppercase; - self.button_ui(s, color, Some(Colors::fill_lite()), &mut columns[index], |_| { - uppercase = !uppercase; + Self::button_ui(s, color, Some(Colors::fill_lite()), &mut columns[index], |_| { + UPPERCASE.store(!uppercase, Ordering::Relaxed); }); - self.uppercase = uppercase; } else if index == tl_3.len() - 1 { // Check for backspace input. - self.button_ui(s, + Self::button_ui(s, Colors::red(), Some(Colors::fill_lite()), &mut columns[index], |_| { - Self::on_input(KeyboardInput::CLEAR); + Self::input_event(KeyboardEvent::CLEAR); }); } else { - self.input_button_ui(s, &mut columns[index]); + Self::input_button_ui(s, &mut columns[index]); } } }); @@ -225,12 +224,12 @@ impl KeyboardContent { } /// Draw symbols content returning button [`Rect`]. - fn symbols_ui(&mut self, ui: &mut egui::Ui) -> Rect { + fn symbols_ui(ui: &mut egui::Ui) -> Rect { let mut button_rect = ui.available_rect_before_wrap(); 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, &mut columns[index]); } }); ui.add_space(View::TAB_ITEMS_PADDING); @@ -238,7 +237,7 @@ impl KeyboardContent { 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, &mut columns[index]); } }); ui.add_space(View::TAB_ITEMS_PADDING); @@ -246,7 +245,7 @@ impl KeyboardContent { 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, &mut columns[index]); } }); ui.add_space(View::TAB_ITEMS_PADDING); @@ -256,14 +255,14 @@ impl KeyboardContent { for (index, s) in tl_3.iter().enumerate() { if index == tl_3.len() - 1 { // Check for backspace input. - self.button_ui(s, + Self::button_ui(s, Colors::red(), Some(Colors::fill_lite()), &mut columns[index], |_| { - Self::on_input(KeyboardInput::CLEAR); + Self::input_event(KeyboardEvent::CLEAR); }); } else { - self.input_button_ui(s, &mut columns[index]); + Self::input_button_ui(s, &mut columns[index]); } } }); @@ -272,8 +271,7 @@ impl KeyboardContent { } /// Draw keyboard button. - fn button_ui(&self, - s: &str, + fn button_ui(s: &str, color: Color32, bg: Option, ui: &mut egui::Ui, @@ -291,7 +289,7 @@ impl KeyboardContent { ui.visuals_mut().widgets.hovered.bg_stroke = View::hover_stroke(); ui.visuals_mut().widgets.active.bg_stroke = Stroke::NONE; - let label = if self.uppercase { + let label = if UPPERCASE.load(Ordering::Relaxed) { s.to_uppercase() } else { s.to_string() @@ -308,32 +306,28 @@ impl KeyboardContent { } /// Draw input button. - fn input_button_ui(&mut self, s: &str, ui: &mut egui::Ui) -> Rect { - let mut input = false; - let rect = self.button_ui(s, Colors::text_button(), None, ui, |l| { - input = true; - Self::on_input(KeyboardInput::TEXT(l)); + fn input_button_ui(s: &str, ui: &mut egui::Ui) -> Rect { + let rect = Self::button_ui(s, Colors::text_button(), None, ui, |l| { + Self::input_event(KeyboardEvent::TEXT(l)); + UPPERCASE.store(false, Ordering::Relaxed); }).rect; - if input { - self.uppercase = false; - } rect } - /// Save keyboard action to consume later. - fn on_input(action: KeyboardInput) { - let mut w_input = LAST_INPUT.write(); - *w_input = Some(action); + /// Save keyboard event to consume later. + fn input_event(event: KeyboardEvent) { + let mut w_input = LAST_EVENT.write(); + *w_input = Some(event); } - /// Check last keyboard input action. - pub fn consume_action() -> Option { + /// Check last keyboard input event. + pub fn consume_event() -> Option { let empty = { - let r_input = LAST_INPUT.read(); + let r_input = LAST_EVENT.read(); r_input.is_none() }; if !empty { - let mut w_input = LAST_INPUT.write(); + let mut w_input = LAST_EVENT.write(); let res = w_input.clone(); *w_input = None; return res; @@ -347,10 +341,21 @@ impl KeyboardContent { } /// Show keyboard. - pub fn show() { + pub fn show(numeric: bool) { + NUMERIC.store(numeric, Ordering::Relaxed); SHOW_KEYBOARD.store(true, Ordering::Relaxed); } + /// Emulate Shift key pressing. + pub fn shift() { + UPPERCASE.store(true, Ordering::Relaxed); + } + + /// Emulate Shift key pressing. + pub fn unshift() { + UPPERCASE.store(false, Ordering::Relaxed); + } + /// Hide keyboard. pub fn hide() { SHOW_KEYBOARD.store(false, Ordering::Relaxed); diff --git a/src/gui/views/input/mod.rs b/src/gui/views/input/mod.rs new file mode 100644 index 00000000..da864160 --- /dev/null +++ b/src/gui/views/input/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2025 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod types; +pub use types::*; + +mod edit; +pub use edit::*; + +mod keyboard; +pub use keyboard::*; \ No newline at end of file diff --git a/src/gui/views/input/types.rs b/src/gui/views/input/types.rs new file mode 100644 index 00000000..42dcd7ac --- /dev/null +++ b/src/gui/views/input/types.rs @@ -0,0 +1,25 @@ +// Copyright 2025 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Software keyboard input type. +#[derive(Clone, PartialOrd, PartialEq)] +pub enum KeyboardLayout { + TEXT, SYMBOLS +} + +/// Software keyboard input event. +#[derive(Clone)] +pub enum KeyboardEvent { + TEXT(String), CLEAR, ENTER +} \ No newline at end of file diff --git a/src/gui/views/mod.rs b/src/gui/views/mod.rs index c54be936..6ebf7562 100644 --- a/src/gui/views/mod.rs +++ b/src/gui/views/mod.rs @@ -45,5 +45,5 @@ pub use pull_to_refresh::*; mod scan; pub use scan::*; -mod keyboard; -pub use keyboard::*; \ No newline at end of file +mod input; +pub use input::*; \ No newline at end of file diff --git a/src/gui/views/modal.rs b/src/gui/views/modal.rs index dd505c62..e9c1e319 100755 --- a/src/gui/views/modal.rs +++ b/src/gui/views/modal.rs @@ -40,6 +40,8 @@ pub struct Modal { closeable: Arc, /// Title text. title: Option, + /// Flag to check first content render. + first_draw: Arc, } impl Modal { @@ -55,6 +57,7 @@ impl Modal { position: ModalPosition::Center, closeable: Arc::new(AtomicBool::new(true)), title: None, + first_draw: Arc::new(AtomicBool::new(true)), } } @@ -74,7 +77,6 @@ impl Modal { pub fn close(&self) { let mut w_nav = MODAL_STATE.write(); w_nav.modal = None; - // Hide keyboard. KeyboardContent::hide(); } @@ -157,7 +159,6 @@ impl Modal { /// Set title text for current opened [`Modal`]. pub fn set_title(title: String) { - // Save state. let mut w_state = MODAL_STATE.write(); if w_state.modal.is_some() { let mut modal = w_state.modal.clone().unwrap(); @@ -166,6 +167,16 @@ impl Modal { } } + /// Check for first [`Modal`] content rendering. + pub fn first_draw() -> bool { + if Self::opened().is_none() { + return false; + } + let r_state = MODAL_STATE.read(); + let modal = r_state.modal.as_ref().unwrap(); + modal.first_draw.load(Ordering::Relaxed) + } + /// Draw opened [`Modal`] content. pub fn ui(ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) { let has_modal = { @@ -247,6 +258,13 @@ impl Modal { // Always show main content window above background window. ctx.move_to_top(layer_id); + + // Setup first draw flag. + if Self::first_draw() { + let r_state = MODAL_STATE.read(); + let modal = r_state.modal.as_ref().unwrap(); + modal.first_draw.store(false, Ordering::Relaxed); + } } /// Get [`egui::Window`] position based on [`ModalPosition`]. diff --git a/src/gui/views/network/modals/ext_conn.rs b/src/gui/views/network/modals/ext_conn.rs index 97e0e51b..b840f901 100644 --- a/src/gui/views/network/modals/ext_conn.rs +++ b/src/gui/views/network/modals/ext_conn.rs @@ -14,16 +14,14 @@ use egui::{Id, RichText}; use url::Url; + use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{Modal, TextEdit, View}; use crate::wallet::{ConnectionsConfig, ExternalConnection}; /// Content to create or update external wallet connection. pub struct ExternalConnectionModal { - /// Flag to check if [`Modal`] was just opened to focus on input field. - first_modal_launch: bool, /// External connection URL value for [`Modal`]. ext_node_url_edit: String, /// External connection API secret value for [`Modal`]. @@ -34,8 +32,6 @@ pub struct ExternalConnectionModal { ext_conn_id: Option, } - - impl ExternalConnectionModal { /// Network [`Modal`] identifier. pub const NETWORK_ID: &'static str = "net_ext_conn_modal"; @@ -50,7 +46,6 @@ impl ExternalConnectionModal { ("".to_string(), "".to_string(), None) }; Self { - first_modal_launch: true, ext_node_url_edit, ext_node_secret_edit, ext_node_url_error: false, @@ -64,23 +59,54 @@ impl ExternalConnectionModal { cb: &dyn PlatformCallbacks, modal: &Modal, on_save: impl Fn(ExternalConnection)) { - ui.add_space(6.0); + // Add connection button callback. + let on_add = |ui: &mut egui::Ui, m: &mut ExternalConnectionModal| { + let url = if !m.ext_node_url_edit.starts_with("http") { + format!("http://{}", m.ext_node_url_edit) + } else { + m.ext_node_url_edit.clone() + }; + let error = Url::parse(url.trim()).is_err(); + m.ext_node_url_error = error; + if !error { + let secret = if m.ext_node_secret_edit.is_empty() { + None + } else { + Some(m.ext_node_secret_edit.clone()) + }; + + // Update or create new connection. + let mut ext_conn = ExternalConnection::new(url, secret); + if let Some(id) = m.ext_conn_id { + ext_conn.id = id; + } + ConnectionsConfig::add_ext_conn(ext_conn.clone()); + ExternalConnection::check(Some(ext_conn.id), ui.ctx()); + on_save(ext_conn); + + // Close modal. + m.ext_node_url_edit = "".to_string(); + m.ext_node_secret_edit = "".to_string(); + m.ext_node_url_error = false; + modal.close(); + } + }; + ui.vertical_centered(|ui| { + ui.add_space(6.0); ui.label(RichText::new(t!("wallets.node_url")) .size(17.0) .color(Colors::gray())); ui.add_space(8.0); // Draw node URL text edit. - let url_edit_id = Id::from(modal.id).with(self.ext_conn_id); - let mut url_edit_opts = TextEditOptions::new(url_edit_id).paste().no_focus(); - if self.first_modal_launch { - self.first_modal_launch = false; - url_edit_opts.focus = true; - } - View::text_edit(ui, cb, &mut self.ext_node_url_edit, &mut url_edit_opts); - ui.add_space(8.0); + let url_edit_id = Id::from(modal.id).with(self.ext_conn_id).with("node_url"); + let mut url_edit = TextEdit::new(url_edit_id) + .paste() + .focus(Modal::first_draw()); + url_edit.ui(ui, &mut self.ext_node_url_edit, cb); + ui.add_space(8.0); ui.label(RichText::new(t!("wallets.node_secret")) .size(17.0) .color(Colors::gray())); @@ -88,8 +114,17 @@ impl ExternalConnectionModal { // Draw node API secret text edit. let secret_edit_id = Id::from(modal.id).with(self.ext_conn_id).with("node_secret"); - let mut secret_edit_opts = TextEditOptions::new(secret_edit_id).paste().no_focus(); - View::text_edit(ui, cb, &mut self.ext_node_secret_edit, &mut secret_edit_opts); + let mut secret_edit = TextEdit::new(secret_edit_id) + .password() + .paste() + .focus(false); + if url_edit.enter_pressed { + secret_edit.focus_request(); + } + secret_edit.ui(ui, &mut self.ext_node_secret_edit, cb); + if secret_edit.enter_pressed { + (on_add)(ui, self); + } // Show error when specified URL is not valid. if self.ext_node_url_error { @@ -117,52 +152,13 @@ impl ExternalConnectionModal { }); }); columns[1].vertical_centered_justified(|ui| { - // Add connection button callback. - let mut on_add = |ui: &mut egui::Ui| { - if !self.ext_node_url_edit.starts_with("http") { - self.ext_node_url_edit = format!("http://{}", self.ext_node_url_edit) - } - let error = Url::parse(self.ext_node_url_edit.as_str()).is_err(); - self.ext_node_url_error = error; - if !error { - let url = self.ext_node_url_edit.to_owned(); - let secret = if self.ext_node_secret_edit.is_empty() { - None - } else { - Some(self.ext_node_secret_edit.to_owned()) - }; - - // Update or create new connection. - let mut ext_conn = ExternalConnection::new(url, secret); - if let Some(id) = self.ext_conn_id { - ext_conn.id = id; - } - ConnectionsConfig::add_ext_conn(ext_conn.clone()); - ExternalConnection::check(Some(ext_conn.id), ui.ctx()); - on_save(ext_conn); - - // Close modal. - self.ext_node_url_edit = "".to_string(); - self.ext_node_secret_edit = "".to_string(); - self.ext_node_url_error = false; - modal.close(); - } - }; - - // Handle Enter key press. - let mut enter = false; - View::on_enter_key(ui, || { - enter = true; - }); - if enter { - (on_add)(ui); - } - View::button_ui(ui, if self.ext_conn_id.is_some() { t!("modal.save") } else { t!("modal.add") - }, Colors::white_or_black(false), on_add); + }, Colors::white_or_black(false), |ui| { + (on_add)(ui, self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/network/setup/dandelion.rs b/src/gui/views/network/setup/dandelion.rs index f3c9c9de..66feae39 100644 --- a/src/gui/views/network/setup/dandelion.rs +++ b/src/gui/views/network/setup/dandelion.rs @@ -14,12 +14,12 @@ use egui::{Id, RichText}; -use crate::gui::Colors; use crate::gui::icons::{CLOCK_COUNTDOWN, GRAPH, TIMER, WATCH}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::network::settings::NetworkSettings; -use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; +use crate::gui::views::types::{ModalContainer, ModalPosition}; +use crate::gui::views::{Modal, TextEdit, View}; +use crate::gui::Colors; +use crate::gui::views::network::NetworkSettings; use crate::node::NodeConfig; /// Dandelion server setup section content. @@ -155,6 +155,14 @@ impl DandelionSetup { /// Draw epoch duration [`Modal`] content. fn epoch_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + // Save button callback. + let on_save = |c: &mut DandelionSetup| { + if let Ok(epoch) = c.epoch_edit.parse::() { + NodeConfig::save_dandelion_epoch(epoch); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.epoch_duration")) @@ -163,8 +171,11 @@ impl DandelionSetup { ui.add_space(8.0); // Draw epoch text edit. - let mut epoch_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.epoch_edit, &mut epoch_edit_opts); + let mut epoch_edit = TextEdit::new(Id::from(modal.id)).h_center(); + epoch_edit.ui(ui, &mut self.epoch_edit, cb); + if epoch_edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.epoch_edit.parse::().is_err() { @@ -183,19 +194,6 @@ impl DandelionSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(epoch) = self.epoch_edit.parse::() { - NodeConfig::save_dandelion_epoch(epoch); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -204,7 +202,9 @@ impl DandelionSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -233,6 +233,14 @@ impl DandelionSetup { /// Draw epoch duration [`Modal`] content. fn embargo_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + // Save button callback. + let on_save = |c: &mut DandelionSetup| { + if let Ok(embargo) = c.embargo_edit.parse::() { + NodeConfig::save_dandelion_embargo(embargo); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.embargo_timer")) @@ -241,8 +249,11 @@ impl DandelionSetup { ui.add_space(8.0); // Draw embargo text edit. - let mut embargo_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.embargo_edit, &mut embargo_edit_opts); + let mut embargo_edit = TextEdit::new(Id::from(modal.id)).h_center(); + embargo_edit.ui(ui, &mut self.embargo_edit, cb); + if embargo_edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.embargo_edit.parse::().is_err() { @@ -261,19 +272,6 @@ impl DandelionSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(embargo) = self.embargo_edit.parse::() { - NodeConfig::save_dandelion_embargo(embargo); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -282,7 +280,9 @@ impl DandelionSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -312,6 +312,14 @@ impl DandelionSetup { /// Draw aggregation period [`Modal`] content. fn aggregation_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + // Save button callback. + let on_save = |c: &mut DandelionSetup| { + if let Ok(embargo) = c.aggregation_edit.parse::() { + NodeConfig::save_dandelion_aggregation(embargo); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.aggregation_period")) @@ -320,8 +328,11 @@ impl DandelionSetup { ui.add_space(8.0); // Draw aggregation period text edit. - let mut aggregation_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.aggregation_edit, &mut aggregation_edit_opts); + let mut aggregation_edit = TextEdit::new(Id::from(modal.id)).h_center(); + aggregation_edit.ui(ui, &mut self.aggregation_edit, cb); + if aggregation_edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.aggregation_edit.parse::().is_err() { @@ -340,19 +351,6 @@ impl DandelionSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(embargo) = self.aggregation_edit.parse::() { - NodeConfig::save_dandelion_aggregation(embargo); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -361,7 +359,9 @@ impl DandelionSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -391,6 +391,14 @@ impl DandelionSetup { /// Draw stem phase probability [`Modal`] content. fn stem_prob_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + // Save button callback. + let on_save = |c: &mut DandelionSetup| { + if let Ok(prob) = c.stem_prob_edit.parse::() { + NodeConfig::save_stem_probability(prob); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.stem_probability")) @@ -399,8 +407,11 @@ impl DandelionSetup { ui.add_space(8.0); // Draw stem phase probability text edit. - let mut stem_prob_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.stem_prob_edit, &mut stem_prob_edit_opts); + let mut stem_prob_edit = TextEdit::new(Id::from(modal.id)).h_center(); + stem_prob_edit.ui(ui, &mut self.stem_prob_edit, cb); + if stem_prob_edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.stem_prob_edit.parse::().is_err() { @@ -419,19 +430,6 @@ impl DandelionSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(prob) = self.stem_prob_edit.parse::() { - NodeConfig::save_stem_probability(prob); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -440,7 +438,9 @@ impl DandelionSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/network/setup/node.rs b/src/gui/views/network/setup/node.rs index 48a3a09d..51b57eb7 100644 --- a/src/gui/views/network/setup/node.rs +++ b/src/gui/views/network/setup/node.rs @@ -15,15 +15,15 @@ use egui::{Id, RichText}; use grin_core::global::ChainTypes; -use crate::AppConfig; -use crate::gui::Colors; use crate::gui::icons::{CLOCK_CLOCKWISE, COMPUTER_TOWER, PLUG, POWER, SHIELD, SHIELD_SLASH}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::network::NetworkContent; use crate::gui::views::network::settings::NetworkSettings; -use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; +use crate::gui::views::network::NetworkContent; +use crate::gui::views::types::{ModalContainer, ModalPosition}; +use crate::gui::views::{Modal, TextEdit, View}; +use crate::gui::Colors; use crate::node::{Node, NodeConfig}; +use crate::AppConfig; /// Integrated node general setup section content. pub struct NodeSetup { @@ -280,6 +280,22 @@ impl NodeSetup { /// Draw API port [`Modal`] content. fn api_port_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut NodeSetup| { + // Check if port is available. + let (api_ip, _) = NodeConfig::get_api_ip_port(); + let available = NodeConfig::is_api_port_available(&api_ip, &c.api_port_edit); + c.api_port_available_edit = available; + if available { + // Save port at config if it's available. + NodeConfig::save_api_address(&api_ip, &c.api_port_edit); + if Node::is_running() { + Node::restart(); + } + c.is_api_port_available = true; + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.api_port")) @@ -288,8 +304,11 @@ impl NodeSetup { ui.add_space(6.0); // Draw API port text edit. - let mut api_port_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.api_port_edit, &mut api_port_edit_opts); + let mut api_port_edit = TextEdit::new(Id::from(modal.id)).h_center(); + api_port_edit.ui(ui, &mut self.api_port_edit, cb); + if api_port_edit.enter_pressed { + on_save(self); + } // Show error when specified port is unavailable or reminder to restart enabled node. if !self.api_port_available_edit { @@ -308,31 +327,6 @@ impl NodeSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let mut on_save = || { - // Check if port is available. - let (api_ip, _) = NodeConfig::get_api_ip_port(); - let available = NodeConfig::is_api_port_available(&api_ip, &self.api_port_edit); - self.api_port_available_edit = available; - - if available { - // Save port at config if it's available. - NodeConfig::save_api_address(&api_ip, &self.api_port_edit); - - if Node::is_running() { - Node::restart(); - } - - self.is_api_port_available = true; - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -340,7 +334,9 @@ impl NodeSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -383,6 +379,19 @@ impl NodeSetup { /// Draw API secret token [`Modal`] content. fn secret_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut NodeSetup| { + let secret = c.secret_edit.clone(); + match modal.id { + API_SECRET_MODAL => { + NodeConfig::save_api_secret(&secret); + } + _ => { + NodeConfig::save_foreign_api_secret(&secret); + } + }; + modal.close(); + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { let description = match modal.id { @@ -393,8 +402,14 @@ impl NodeSetup { ui.add_space(8.0); // Draw API secret token value text edit. - let mut secret_edit_opts = TextEditOptions::new(Id::from(modal.id)).copy().paste(); - View::text_edit(ui, cb, &mut self.secret_edit, &mut secret_edit_opts); + let mut secret_edit = TextEdit::new(Id::from(modal.id)) + .copy() + .paste(); + secret_edit.ui(ui, &mut self.secret_edit, cb); + if secret_edit.enter_pressed { + on_save(self); + } + ui.add_space(6.0); // Show reminder to restart enabled node. @@ -413,25 +428,6 @@ impl NodeSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - let secret = self.secret_edit.clone(); - match modal.id { - API_SECRET_MODAL => { - NodeConfig::save_api_secret(&secret); - } - _ => { - NodeConfig::save_foreign_api_secret(&secret); - } - }; - modal.close(); - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -439,7 +435,9 @@ impl NodeSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -475,6 +473,14 @@ impl NodeSetup { /// Draw FTL [`Modal`] content. fn ftl_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + // Save button callback. + let on_save = |c: &mut NodeSetup| { + if let Ok(ftl) = c.ftl_edit.parse::() { + NodeConfig::save_ftl(ftl); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.ftl")) @@ -483,8 +489,11 @@ impl NodeSetup { ui.add_space(8.0); // Draw ftl value text edit. - let mut ftl_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.ftl_edit, &mut ftl_edit_opts); + let mut ftl_edit = TextEdit::new(Id::from(modal.id)).h_center(); + ftl_edit.ui(ui, &mut self.ftl_edit, cb); + if ftl_edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.ftl_edit.parse::().is_err() { @@ -503,19 +512,6 @@ impl NodeSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(ftl) = self.ftl_edit.parse::() { - NodeConfig::save_ftl(ftl); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -524,7 +520,9 @@ impl NodeSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/network/setup/p2p.rs b/src/gui/views/network/setup/p2p.rs index 04d3a21d..e0508df8 100644 --- a/src/gui/views/network/setup/p2p.rs +++ b/src/gui/views/network/setup/p2p.rs @@ -19,9 +19,9 @@ use crate::AppConfig; use crate::gui::Colors; use crate::gui::icons::{ARROW_FAT_LINES_DOWN, ARROW_FAT_LINES_UP, GLOBE_SIMPLE, HANDSHAKE, PLUG, PLUS_CIRCLE, PROHIBIT_INSET, TRASH}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; +use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::network::settings::NetworkSettings; -use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; +use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::node::{Node, NodeConfig, PeersConfig}; /// Type of peer. @@ -272,6 +272,22 @@ impl P2PSetup { /// Draw p2p port [`Modal`] content. fn port_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut P2PSetup| { + // Check if port is available. + let available = NodeConfig::is_p2p_port_available(&c.port_edit); + c.port_available_edit = available; + + // Save port at config if it's available. + if available { + NodeConfig::save_p2p_port(c.port_edit.parse::().unwrap()); + if Node::is_running() { + Node::restart(); + } + c.is_port_available = true; + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.p2p_port")) @@ -280,8 +296,11 @@ impl P2PSetup { ui.add_space(8.0); // Draw p2p port text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.port_edit, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.port_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified port is unavailable. if !self.port_available_edit { @@ -298,28 +317,6 @@ impl P2PSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let mut on_save = || { - // Check if port is available. - let available = NodeConfig::is_p2p_port_available(&self.port_edit); - self.port_available_edit = available; - - // Save port at config if it's available. - if available { - NodeConfig::save_p2p_port(self.port_edit.parse::().unwrap()); - if Node::is_running() { - Node::restart(); - } - self.is_port_available = true; - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -328,7 +325,9 @@ impl P2PSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -413,6 +412,27 @@ impl P2PSetup { /// Draw peer creation [`Modal`] content. fn peer_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut P2PSetup| { + // Check if peer is correct and/or available. + let peer = c.peer_edit.clone(); + let is_correct_address = PeersConfig::peer_to_addr(peer.clone()).is_some(); + c.is_correct_address_edit = is_correct_address; + + // Save peer at config. + if is_correct_address { + match modal.id { + CUSTOM_SEED_MODAL => NodeConfig::save_custom_seed(peer), + ALLOW_PEER_MODAL => NodeConfig::allow_peer(peer), + DENY_PEER_MODAL => NodeConfig::deny_peer(peer), + PREFER_PEER_MODAL => NodeConfig::prefer_peer(peer), + &_ => {} + } + + c.is_port_available = true; + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { let label_text = match modal.id { @@ -423,8 +443,11 @@ impl P2PSetup { ui.add_space(8.0); // Draw peer address text edit. - let mut peer_text_edit_opts = TextEditOptions::new(Id::from(modal.id)).paste(); - View::text_edit(ui, cb, &mut self.peer_edit, &mut peer_text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).paste(); + edit.ui(ui, &mut self.peer_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified address is incorrect. if !self.is_correct_address_edit { @@ -440,33 +463,6 @@ impl P2PSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let mut on_save = || { - // Check if peer is correct and/or available. - let peer = self.peer_edit.clone(); - let is_correct_address = PeersConfig::peer_to_addr(peer.clone()).is_some(); - self.is_correct_address_edit = is_correct_address; - - // Save peer at config. - if is_correct_address { - match modal.id { - CUSTOM_SEED_MODAL => NodeConfig::save_custom_seed(peer), - ALLOW_PEER_MODAL => NodeConfig::allow_peer(peer), - DENY_PEER_MODAL => NodeConfig::deny_peer(peer), - PREFER_PEER_MODAL => NodeConfig::prefer_peer(peer), - &_ => {} - } - - self.is_port_available = true; - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -475,7 +471,9 @@ impl P2PSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -533,6 +531,13 @@ impl P2PSetup { /// Draw ban window [`Modal`] content. fn ban_window_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut P2PSetup| { + if let Ok(ban_window) = c.ban_window_edit.parse::() { + NodeConfig::save_p2p_ban_window(ban_window); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.ban_window")) @@ -541,8 +546,11 @@ impl P2PSetup { ui.add_space(8.0); // Draw ban window text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.ban_window_edit, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.ban_window_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.ban_window_edit.parse::().is_err() { @@ -561,28 +569,16 @@ impl P2PSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(ban_window) = self.ban_window_edit.parse::() { - NodeConfig::save_p2p_ban_window(ban_window); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { - // Close modal. modal.close(); }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -614,6 +610,13 @@ impl P2PSetup { /// Draw maximum number of inbound peers [`Modal`] content. fn max_inbound_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut P2PSetup| { + if let Ok(max_inbound) = c.max_inbound_count.parse::() { + NodeConfig::save_max_inbound_peers(max_inbound); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.max_inbound_count")) @@ -622,8 +625,11 @@ impl P2PSetup { ui.add_space(8.0); // Draw maximum number of inbound peers text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.max_inbound_count, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.max_inbound_count, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.max_inbound_count.parse::().is_err() { @@ -642,28 +648,16 @@ impl P2PSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(max_inbound) = self.max_inbound_count.parse::() { - NodeConfig::save_max_inbound_peers(max_inbound); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { - // Close modal. modal.close(); }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -694,6 +688,13 @@ impl P2PSetup { /// Draw maximum number of outbound peers [`Modal`] content. fn max_outbound_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut P2PSetup| { + if let Ok(max_outbound) = c.max_outbound_count.parse::() { + NodeConfig::save_max_outbound_peers(max_outbound); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.max_outbound_count")) @@ -702,8 +703,11 @@ impl P2PSetup { ui.add_space(8.0); // Draw maximum number of outbound peers text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.max_outbound_count, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.max_outbound_count, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.max_outbound_count.parse::().is_err() { @@ -722,19 +726,6 @@ impl P2PSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(max_outbound) = self.max_outbound_count.parse::() { - NodeConfig::save_max_outbound_peers(max_outbound); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -743,7 +734,9 @@ impl P2PSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/network/setup/pool.rs b/src/gui/views/network/setup/pool.rs index 70b3ac00..e4180234 100644 --- a/src/gui/views/network/setup/pool.rs +++ b/src/gui/views/network/setup/pool.rs @@ -17,9 +17,9 @@ use egui::{Id, RichText}; use crate::gui::Colors; use crate::gui::icons::{BEZIER_CURVE, BOUNDING_BOX, CHART_SCATTER, CIRCLES_THREE, CLOCK_COUNTDOWN, HAND_COINS}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; +use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::network::settings::NetworkSettings; -use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; +use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::node::NodeConfig; /// Memory pool setup section content. @@ -159,6 +159,13 @@ impl PoolSetup { /// Draw fee base [`Modal`] content. fn fee_base_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut PoolSetup| { + if let Ok(fee) = c.fee_base_edit.parse::() { + NodeConfig::save_base_fee(fee); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.pool_fee")) @@ -167,8 +174,9 @@ impl PoolSetup { ui.add_space(8.0); // Draw fee base text edit. - let mut fee_base_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.fee_base_edit, &mut fee_base_edit_opts); + TextEdit::new(Id::from(modal.id)) + .h_center() + .ui(ui, &mut self.fee_base_edit, cb); // Show error when specified value is not valid or reminder to restart enabled node. if self.fee_base_edit.parse::().is_err() { @@ -186,19 +194,6 @@ impl PoolSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(fee) = self.fee_base_edit.parse::() { - NodeConfig::save_base_fee(fee); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -207,7 +202,9 @@ impl PoolSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -239,6 +236,13 @@ impl PoolSetup { /// Draw reorg cache retention period [`Modal`] content. fn reorg_period_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut PoolSetup| { + if let Ok(period) = c.reorg_period_edit.parse::() { + NodeConfig::save_reorg_cache_period(period); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.reorg_period")) @@ -247,8 +251,11 @@ impl PoolSetup { ui.add_space(8.0); // Draw reorg period text edit. - let mut reorg_period_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.reorg_period_edit, &mut reorg_period_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.reorg_period_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.reorg_period_edit.parse::().is_err() { @@ -266,19 +273,6 @@ impl PoolSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(period) = self.reorg_period_edit.parse::() { - NodeConfig::save_reorg_cache_period(period); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -287,7 +281,9 @@ impl PoolSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -318,6 +314,13 @@ impl PoolSetup { /// Draw maximum number of transactions in the pool [`Modal`] content. fn pool_size_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut PoolSetup| { + if let Ok(size) = c.pool_size_edit.parse::() { + NodeConfig::save_max_pool_size(size); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.max_tx_pool")) @@ -326,8 +329,12 @@ impl PoolSetup { ui.add_space(8.0); // Draw pool size text edit. - let mut pool_size_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.pool_size_edit, &mut pool_size_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)) + .h_center(); + edit.ui(ui, &mut self.pool_size_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.pool_size_edit.parse::().is_err() { @@ -345,19 +352,6 @@ impl PoolSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(size) = self.pool_size_edit.parse::() { - NodeConfig::save_max_pool_size(size); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -366,7 +360,9 @@ impl PoolSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -399,6 +395,13 @@ impl PoolSetup { /// Draw maximum number of transactions in the stempool [`Modal`] content. fn stem_size_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut PoolSetup| { + if let Ok(size) = c.stempool_size_edit.parse::() { + NodeConfig::save_max_stempool_size(size); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.max_tx_stempool")) @@ -407,8 +410,11 @@ impl PoolSetup { ui.add_space(8.0); // Draw stempool size text edit. - let mut stem_pool_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.stempool_size_edit, &mut stem_pool_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.stempool_size_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.stempool_size_edit.parse::().is_err() { @@ -426,19 +432,6 @@ impl PoolSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(size) = self.stempool_size_edit.parse::() { - NodeConfig::save_max_stempool_size(size); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -447,7 +440,9 @@ impl PoolSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -480,6 +475,13 @@ impl PoolSetup { /// Draw maximum total weight of transactions [`Modal`] content. fn max_weight_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut PoolSetup| { + if let Ok(weight) = c.max_weight_edit.parse::() { + NodeConfig::save_mineable_max_weight(weight); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.max_tx_weight")) @@ -488,8 +490,11 @@ impl PoolSetup { ui.add_space(8.0); // Draw tx weight text edit. - let mut mac_weight_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.max_weight_edit, &mut mac_weight_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.max_weight_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.max_weight_edit.parse::().is_err() { @@ -507,19 +512,6 @@ impl PoolSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(weight) = self.max_weight_edit.parse::() { - NodeConfig::save_mineable_max_weight(weight); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -528,7 +520,9 @@ impl PoolSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/network/setup/stratum.rs b/src/gui/views/network/setup/stratum.rs index a06a4e71..0893fa9b 100644 --- a/src/gui/views/network/setup/stratum.rs +++ b/src/gui/views/network/setup/stratum.rs @@ -18,9 +18,9 @@ use grin_chain::SyncStatus; use crate::gui::Colors; use crate::gui::icons::{BARBELL, HARD_DRIVES, PLUG, POWER, TIMER}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; +use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::network::settings::NetworkSettings; -use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; +use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::gui::views::wallets::modals::WalletsModal; use crate::node::{Node, NodeConfig}; use crate::wallet::{WalletConfig, WalletList}; @@ -286,6 +286,24 @@ impl StratumSetup { /// Draw stratum port [`Modal`] content. fn port_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut StratumSetup| { + // Check if port is available. + let (stratum_ip, _) = NodeConfig::get_stratum_address(); + let available = NodeConfig::is_stratum_port_available( + &stratum_ip, + &c.stratum_port_edit + ); + c.stratum_port_available_edit = available; + + // Save port at config if it's available. + if available { + NodeConfig::save_stratum_address(&stratum_ip, &c.stratum_port_edit); + + c.is_port_available = true; + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.stratum_port")) @@ -294,8 +312,11 @@ impl StratumSetup { ui.add_space(8.0); // Draw stratum port text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.stratum_port_edit, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.stratum_port_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified port is unavailable. if !self.stratum_port_available_edit { @@ -314,30 +335,6 @@ impl StratumSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let mut on_save = || { - // Check if port is available. - let (stratum_ip, _) = NodeConfig::get_stratum_address(); - let available = NodeConfig::is_stratum_port_available( - &stratum_ip, - &self.stratum_port_edit - ); - self.stratum_port_available_edit = available; - - // Save port at config if it's available. - if available { - NodeConfig::save_stratum_address(&stratum_ip, &self.stratum_port_edit); - - self.is_port_available = true; - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -346,7 +343,9 @@ impl StratumSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -383,6 +382,13 @@ impl StratumSetup { /// Draw attempt time [`Modal`] content. fn attempt_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut StratumSetup| { + if let Ok(time) = c.attempt_time_edit.parse::() { + NodeConfig::save_stratum_attempt_time(time); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.attempt_time")) @@ -391,8 +397,11 @@ impl StratumSetup { ui.add_space(8.0); // Draw attempt time text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.attempt_time_edit, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.attempt_time_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.attempt_time_edit.parse::().is_err() { @@ -411,19 +420,6 @@ impl StratumSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(time) = self.attempt_time_edit.parse::() { - NodeConfig::save_stratum_attempt_time(time); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -432,7 +428,9 @@ impl StratumSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); @@ -463,6 +461,13 @@ impl StratumSetup { /// Draw minimum acceptable share difficulty [`Modal`] content. fn min_diff_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut StratumSetup| { + if let Ok(diff) = c.min_share_diff_edit.parse::() { + NodeConfig::save_stratum_min_share_diff(diff); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("network_settings.min_share_diff")) @@ -471,8 +476,11 @@ impl StratumSetup { ui.add_space(8.0); // Draw share difficulty text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.min_share_diff_edit, &mut text_edit_opts); + let mut edit = TextEdit::new(Id::from(modal.id)).h_center(); + edit.ui(ui, &mut self.min_share_diff_edit, cb); + if edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.min_share_diff_edit.parse::().is_err() { @@ -491,19 +499,6 @@ impl StratumSetup { // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); - // Save button callback. - let on_save = || { - if let Ok(diff) = self.min_share_diff_edit.parse::() { - NodeConfig::save_stratum_min_share_diff(diff); - modal.close(); - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - on_save(); - }); - ui.columns(2, |columns| { columns[0].vertical_centered_justified(|ui| { View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || { @@ -512,7 +507,9 @@ impl StratumSetup { }); }); columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/types.rs b/src/gui/views/types.rs index afa4d1eb..fcaaf121 100644 --- a/src/gui/views/types.rs +++ b/src/gui/views/types.rs @@ -76,81 +76,6 @@ pub trait ModalContainer { } } -/// Options for [`egui::TextEdit`] view. -pub struct TextEditOptions { - /// View identifier. - pub id: egui::Id, - /// Check if horizontal centering is needed. - pub h_center: bool, - /// Check if initial focus on field is needed. - pub focus: bool, - /// Hide letters and draw button to show/hide letters. - pub password: bool, - /// Show copy button. - pub copy: bool, - /// Show paste button. - pub paste: bool, - /// Show button to scan QR code into text. - pub scan_qr: bool, - /// Callback when scan button was pressed. - pub scan_pressed: bool, - /// Callback when Enter key was pressed. - pub enter_pressed: bool, -} - -impl TextEditOptions { - pub fn new(id: egui::Id) -> Self { - Self { - id, - h_center: false, - focus: true, - password: false, - copy: false, - paste: false, - scan_qr: false, - scan_pressed: false, - enter_pressed: false, - } - } - - /// Center text horizontally. - pub fn h_center(mut self) -> Self { - self.h_center = true; - self - } - - /// Disable initial focus. - pub fn no_focus(mut self) -> Self { - self.focus = false; - self - } - - /// Hide letters and draw button to show/hide letters. - pub fn password(mut self) -> Self { - self.password = true; - self - } - - /// Show button to copy text. - pub fn copy(mut self) -> Self { - self.copy = true; - self - } - - /// Show button to paste text. - pub fn paste(mut self) -> Self { - self.paste = true; - self - } - - /// Show button to scan QR code to text. - pub fn scan_qr(mut self) -> Self { - self.scan_qr = true; - self.scan_pressed = false; - self - } -} - /// QR code scan result. #[derive(Clone)] pub enum QrScanResult { diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index 730132d7..acba1f75 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -12,24 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::atomic::{AtomicI32, Ordering}; use lazy_static::lazy_static; +use std::sync::atomic::{AtomicI32, Ordering}; -use egui::{Align, Button, CursorIcon, Layout, lerp, PointerState, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, TextBuffer, TextStyle, TextureHandle, TextureOptions, Widget, UiBuilder}; -use egui::epaint::{Color32, FontId, PathShape, PathStroke, RectShape, Rounding, Stroke}; use egui::epaint::text::TextWrapping; +use egui::epaint::{Color32, FontId, PathShape, PathStroke, RectShape, Rounding, Stroke}; use egui::load::SizedTexture; use egui::os::OperatingSystem; use egui::text::{LayoutJob, TextFormat}; -use egui::text_edit::TextEditState; +use egui::{lerp, Button, CursorIcon, PointerState, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, TextureHandle, TextureOptions, UiBuilder, Widget}; use egui_extras::image::load_svg_bytes_with_size; -use crate::AppConfig; +use crate::gui::icons::{CHECK_SQUARE, SQUARE}; +use crate::gui::views::types::LinePosition; use crate::gui::Colors; -use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN, SQUARE}; -use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{KeyboardContent, KeyboardInput}; -use crate::gui::views::types::{LinePosition, TextEditOptions}; +use crate::AppConfig; pub struct View; @@ -90,13 +87,6 @@ impl View { (rect.width(), rect.height()) } - /// Callback on Enter key press event. - pub fn on_enter_key(ui: &mut egui::Ui, cb: impl FnOnce()) { - if ui.ctx().input(|i| i.key_pressed(egui::Key::Enter)) { - (cb)(); - } - } - /// Calculate margin for far left view based on display insets (cutouts). pub fn far_left_inset_margin(ui: &mut egui::Ui) -> f32 { if ui.available_rect_before_wrap().min.x == 0.0 { @@ -159,7 +149,7 @@ impl View { } } - /// Draw horizontally centered sub-title with space below. + /// Draw horizontally centered subtitle with space below. pub fn sub_title(ui: &mut egui::Ui, text: String) { ui.vertical_centered_justified(|ui| { ui.label(RichText::new(text.to_uppercase()).size(16.0).color(Colors::text(false))); @@ -369,162 +359,6 @@ impl View { }); } - /// Default height of [`egui::TextEdit`] view. - const TEXT_EDIT_HEIGHT: f32 = 37.0; - - /// Draw [`egui::TextEdit`] widget. - pub fn text_edit(ui: &mut egui::Ui, - cb: &dyn PlatformCallbacks, - value: &mut String, - options: &mut TextEditOptions) { - let mut layout_rect = ui.available_rect_before_wrap(); - layout_rect.set_height(Self::TEXT_EDIT_HEIGHT); - ui.allocate_ui_with_layout(layout_rect.size(), Layout::right_to_left(Align::Center), |ui| { - // Setup password button. - let mut show_pass = false; - if options.password { - // Set password button state value. - let show_pass_id = egui::Id::new(options.id).with("_show_pass"); - show_pass = ui.data(|data| { - data.get_temp(show_pass_id) - }).unwrap_or(true); - // Draw button to show/hide current password. - let eye_icon = if show_pass { EYE } else { EYE_SLASH }; - let mut changed = false; - View::button(ui, eye_icon.to_string(), Colors::white_or_black(false), || { - show_pass = !show_pass; - changed = true; - }); - // Save state if changed. - if changed { - ui.data_mut(|data| { - data.insert_temp(show_pass_id, show_pass); - }); - } - ui.add_space(8.0); - } - - // Setup copy button. - if options.copy { - let copy_icon = COPY.to_string(); - View::button(ui, copy_icon, Colors::white_or_black(false), || { - cb.copy_string_to_buffer(value.clone()); - }); - ui.add_space(8.0); - } - - // Setup paste button. - if options.paste { - let paste_icon = CLIPBOARD_TEXT.to_string(); - View::button(ui, paste_icon, Colors::white_or_black(false), || { - *value = cb.get_string_from_buffer(); - }); - ui.add_space(8.0); - } - - // Setup scan QR code button. - if options.scan_qr { - let scan_icon = SCAN.to_string(); - View::button(ui, scan_icon, Colors::white_or_black(false), || { - cb.start_camera(); - options.scan_pressed = true; - }); - ui.add_space(8.0); - } - - let layout_size = ui.available_size(); - ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| { - // Setup text edit size. - let mut edit_rect = ui.available_rect_before_wrap(); - edit_rect.set_height(Self::TEXT_EDIT_HEIGHT); - - // Show text edit. - let text_edit_resp = egui::TextEdit::singleline(value) - .id(options.id) - .margin(egui::Vec2::new(2.0, 0.0)) - .font(TextStyle::Heading) - .min_size(edit_rect.size()) - .horizontal_align(if options.h_center { Align::Center } else { Align::Min }) - .vertical_align(Align::Center) - .password(show_pass) - .cursor_at_end(true) - .ui(ui); - // Store focusing state on click or from options. - let focus_id = egui::Id::new("focused_input"); - if text_edit_resp.clicked() || options.focus { - ui.data_mut(|data| { - data.insert_temp(focus_id, options.id); - }); - } - let focus = ui.data(|data| { - data.get_temp(focus_id) - }).unwrap_or(egui::Id::from("")); - if focus == options.id { - text_edit_resp.request_focus(); - KeyboardContent::show(); - } - // Apply text from software input. - if text_edit_resp.has_focus() { - options.focus = true; - options.enter_pressed = Self::on_soft_input(ui, options.id, false, value); - } - }); - }); - } - - /// Apply soft keyboard input data to provided String, returns `true` if Enter was pressed. - fn on_soft_input(ui: &mut egui::Ui, id: egui::Id, multiline: bool, value: &mut String) -> bool { - if let Some(input) = KeyboardContent::consume_action() { - let mut enter_pressed = false; - let mut state = TextEditState::load(ui.ctx(), id).unwrap(); - match state.cursor.char_range() { - None => {} - Some(range) => { - let mut r = range.clone(); - let mut index = r.primary.index; - match input { - KeyboardInput::TEXT(text) => { - value.insert_text(text.as_str(), index); - index = index + 1; - } - KeyboardInput::CLEAR => { - if index != 0 { - *value = { - let part1: String = value.chars() - .skip(0) - .take(index - 1) - .collect(); - let part2: String = value.chars() - .skip(index) - .take(value.len() - index) - .collect(); - format!("{}{}", part1, part2) - }; - index = index - 1; - } - } - KeyboardInput::ENTER => { - if multiline { - value.insert_text("\n", index); - index = index + 1; - } else { - enter_pressed = true; - } - } - } - // Setup cursor index. - r.primary.index = index; - r.secondary.index = r.primary.index; - - state.cursor.set_char_range(Some(r)); - TextEditState::store(state, ui.ctx(), id); - } - } - return enter_pressed; - } - false - } - /// Calculate item background/button rounding based on item index. pub fn item_rounding(index: usize, len: usize, is_button: bool) -> Rounding { let corners = if is_button { diff --git a/src/gui/views/wallets/creation/mnemonic.rs b/src/gui/views/wallets/creation/mnemonic.rs index 835bd2eb..df6f0717 100644 --- a/src/gui/views/wallets/creation/mnemonic.rs +++ b/src/gui/views/wallets/creation/mnemonic.rs @@ -17,8 +17,8 @@ use egui::{Id, RichText}; use crate::gui::Colors; use crate::gui::icons::PENCIL; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, Content, View}; -use crate::gui::views::types::{ModalContainer, ModalPosition, TextEditOptions}; +use crate::gui::views::{Modal, Content, View, TextEdit}; +use crate::gui::views::types::{ModalContainer, ModalPosition}; use crate::wallet::Mnemonic; use crate::wallet::types::{PhraseMode, PhraseSize, PhraseWord}; @@ -242,6 +242,26 @@ impl MnemonicSetup { /// Draw word input [`Modal`] content. fn word_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut MnemonicSetup| { + // Insert word checking validity. + let word = &c.word_edit.trim().to_string(); + c.valid_word_edit = c.mnemonic.insert(c.word_index_edit, word); + if !c.valid_word_edit { + return; + } + // Close modal or go to next word to edit. + let next_word = c.mnemonic.get(c.word_index_edit + 1); + let close_modal = next_word.is_none() || + (!next_word.as_ref().unwrap().text.is_empty() && + next_word.unwrap().valid); + if close_modal { + modal.close(); + } else { + c.word_index_edit += 1; + c.word_edit = String::from(""); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("wallets.enter_word", "number" => self.word_index_edit + 1)) @@ -250,10 +270,11 @@ impl MnemonicSetup { ui.add_space(8.0); // Draw word value text edit. - let mut text_edit_opts = TextEditOptions::new( - Id::from(modal.id).with(self.word_index_edit) - ); - View::text_edit(ui, cb, &mut self.word_edit, &mut text_edit_opts); + let mut word_edit = TextEdit::new(Id::from(modal.id).with(self.word_index_edit)); + word_edit.ui(ui, &mut self.word_edit, cb); + if word_edit.enter_pressed { + on_save(self); + } // Show error when specified word is not valid. if !self.valid_word_edit { @@ -278,32 +299,10 @@ impl MnemonicSetup { }); }); columns[1].vertical_centered_justified(|ui| { - // Callback to save the word. - let mut save = || { - // Insert word checking validity. - let word = &self.word_edit.trim().to_string(); - self.valid_word_edit = self.mnemonic.insert(self.word_index_edit, word); - if !self.valid_word_edit { - return; - } - // Close modal or go to next word to edit. - let next_word = self.mnemonic.get(self.word_index_edit + 1); - let close_modal = next_word.is_none() || - (!next_word.as_ref().unwrap().text.is_empty() && - next_word.unwrap().valid); - if close_modal { - modal.close(); - } else { - self.word_index_edit += 1; - self.word_edit = String::from(""); - } - }; - // Call save on Enter key press. - View::on_enter_key(ui, || { - (save)(); - }); // Show save button. - View::button(ui, t!("continue"), Colors::white_or_black(false), save); + View::button(ui, t!("continue"), Colors::white_or_black(false), || { + on_save(self); + }); }); }); ui.add_space(6.0); diff --git a/src/gui/views/wallets/modals/add.rs b/src/gui/views/wallets/modals/add.rs index ade55dc2..36aff20b 100644 --- a/src/gui/views/wallets/modals/add.rs +++ b/src/gui/views/wallets/modals/add.rs @@ -17,13 +17,10 @@ use grin_util::ZeroingString; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{Modal, TextEdit, View}; /// Initial wallet creation [`Modal`] content. pub struct AddWalletModal { - /// Flag to check if it's first draw to focus on first field. - first_draw: bool, /// Wallet name. pub name_edit: String, /// Password to encrypt created wallet. @@ -33,7 +30,6 @@ pub struct AddWalletModal { impl Default for AddWalletModal { fn default() -> Self { Self { - first_draw: true, name_edit: t!("wallets.default_wallet"), pass_edit: "".to_string(), } @@ -47,26 +43,27 @@ impl AddWalletModal { modal: &Modal, cb: &dyn PlatformCallbacks, mut on_input: impl FnMut(String, ZeroingString)) { - ui.add_space(6.0); - - let mut enter_pressed = false; + let mut on_next = |m: &mut AddWalletModal| { + let name = m.name_edit.clone(); + let pass = m.pass_edit.clone(); + if name.is_empty() || pass.is_empty() { + return; + } + modal.close(); + on_input(name, ZeroingString::from(pass)); + }; ui.vertical_centered(|ui| { + ui.add_space(6.0); ui.label(RichText::new(t!("wallets.name")) .size(17.0) .color(Colors::gray())); ui.add_space(8.0); // Show wallet name text edit. - let mut name_edit_opts = TextEditOptions::new(Id::from(modal.id).with("name")) - .no_focus(); - if self.first_draw { - self.first_draw = false; - name_edit_opts.focus = true; - } - View::text_edit(ui, cb, &mut self.name_edit, &mut name_edit_opts); - View::on_enter_key(ui, || { - name_edit_opts.enter_pressed = name_edit_opts.focus; - }); + let mut name_input = TextEdit::new(Id::from(modal.id).with("name")) + .focus(Modal::first_draw()); + + name_input.ui(ui, &mut self.name_edit, cb); ui.add_space(8.0); ui.label(RichText::new(t!("wallets.pass")) @@ -74,16 +71,17 @@ impl AddWalletModal { .color(Colors::gray())); ui.add_space(8.0); - // Draw wallet password text edit. - let mut pass_text_edit_opts = TextEditOptions::new(Id::from(modal.id).with("pass")) + // Show wallet password text edit. + let mut pass_input = TextEdit::new(Id::from(modal.id).with("pass")) .password() - .no_focus(); - if name_edit_opts.enter_pressed { - pass_text_edit_opts.focus = true; + .focus(false); + if name_input.enter_pressed { + pass_input.focus_request(); + } + pass_input.ui(ui, &mut self.pass_edit, cb); + if pass_input.enter_pressed { + (on_next)(self); } - View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_text_edit_opts); - enter_pressed = pass_text_edit_opts.enter_pressed; - ui.add_space(12.0); }); @@ -100,25 +98,9 @@ impl AddWalletModal { }); }); columns[1].vertical_centered_justified(|ui| { - let mut on_next = || { - let name = self.name_edit.clone(); - let pass = self.pass_edit.clone(); - if name.is_empty() || pass.is_empty() { - return; - } - modal.close(); - on_input(name, ZeroingString::from(pass)); - }; - - // Go to next creation step on Enter button press. - View::on_enter_key(ui, || { - (on_next)(); + View::button(ui, t!("continue"), Colors::white_or_black(false), || { + (on_next)(self); }); - if enter_pressed { - (on_next)(); - } - - View::button(ui, t!("continue"), Colors::white_or_black(false), on_next); }); }); ui.add_space(6.0); diff --git a/src/gui/views/wallets/modals/open.rs b/src/gui/views/wallets/modals/open.rs index 3f4149cd..c95dcfd0 100644 --- a/src/gui/views/wallets/modals/open.rs +++ b/src/gui/views/wallets/modals/open.rs @@ -17,8 +17,7 @@ use grin_util::ZeroingString; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{Modal, TextEdit, View}; use crate::wallet::Wallet; /// Wallet opening [`Modal`] content. @@ -51,19 +50,35 @@ impl OpenWalletModal { modal: &Modal, cb: &dyn PlatformCallbacks, mut on_continue: impl FnMut(Wallet, Option)) { - ui.add_space(6.0); - - let mut enter_pressed = false; + // Callback for button to continue. + let mut on_continue = |m: &mut OpenWalletModal| { + let pass = m.pass_edit.clone(); + if pass.is_empty() { + return; + } + match m.wallet.open(ZeroingString::from(pass)) { + Ok(_) => { + m.pass_edit = "".to_string(); + modal.close(); + on_continue(m.wallet.clone(), m.data.clone()); + } + Err(_) => m.wrong_pass = true + } + }; + ui.vertical_centered(|ui| { + ui.add_space(6.0); ui.label(RichText::new(t!("wallets.pass")) .size(17.0) .color(Colors::gray())); ui.add_space(8.0); // Show password input. - let mut pass_edit_opts = TextEditOptions::new(Id::from(modal.id)).password(); - View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_edit_opts); - enter_pressed = pass_edit_opts.enter_pressed; + let mut pass_edit = TextEdit::new(Id::from(modal.id).with("pass_edit")).password(); + pass_edit.ui(ui, &mut self.pass_edit, cb); + if pass_edit.enter_pressed { + (on_continue)(self); + } // Show information when password is empty. if self.pass_edit.is_empty() { @@ -94,31 +109,9 @@ impl OpenWalletModal { }); }); columns[1].vertical_centered_justified(|ui| { - // Callback for button to continue. - let mut on_continue = || { - let pass = self.pass_edit.clone(); - if pass.is_empty() { - return; - } - match self.wallet.open(ZeroingString::from(pass)) { - Ok(_) => { - self.pass_edit = "".to_string(); - modal.close(); - on_continue(self.wallet.clone(), self.data.clone()); - } - Err(_) => self.wrong_pass = true - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - (on_continue)(); + View::button(ui, t!("continue"), Colors::white_or_black(false), || { + (on_continue)(self); }); - if enter_pressed { - (on_continue)(); - } - - View::button(ui, t!("continue"), Colors::white_or_black(false), on_continue); }); }); ui.add_space(6.0); diff --git a/src/gui/views/wallets/wallet/messages/request.rs b/src/gui/views/wallets/wallet/messages/request.rs index 7fcfc60f..15fba258 100644 --- a/src/gui/views/wallets/wallet/messages/request.rs +++ b/src/gui/views/wallets/wallet/messages/request.rs @@ -21,8 +21,7 @@ use grin_wallet_libwallet::Error; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::wallets::wallet::WalletTransactionModal; use crate::wallet::types::WalletTransaction; use crate::wallet::Wallet; @@ -163,10 +162,10 @@ impl MessageRequestModal { ui.add_space(8.0); // Draw request amount text input. - let amount_edit_id = Id::from(modal.id).with(wallet.get_config().id); - let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center(); + let mut amount_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id)) + .h_center(); let amount_edit_before = self.amount_edit.clone(); - View::text_edit(ui, cb, &mut self.amount_edit, &mut amount_edit_opts); + amount_edit.ui(ui, &mut self.amount_edit, cb); // Check value if input was changed. if amount_edit_before != self.amount_edit { diff --git a/src/gui/views/wallets/wallet/modals/accounts.rs b/src/gui/views/wallets/wallet/modals/accounts.rs index 15f52a54..58ccb7a6 100644 --- a/src/gui/views/wallets/wallet/modals/accounts.rs +++ b/src/gui/views/wallets/wallet/modals/accounts.rs @@ -19,8 +19,7 @@ use grin_core::core::amount_to_hr_string; use crate::gui::Colors; use crate::gui::icons::{CHECK, CHECK_FAT, FOLDER_USER, PATH}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{Modal, TextEdit, View}; use crate::gui::views::wallets::wallet::types::GRIN; use crate::wallet::types::WalletAccount; use crate::wallet::{Wallet, WalletConfig}; @@ -66,6 +65,20 @@ impl WalletAccountsModal { modal: &Modal, cb: &dyn PlatformCallbacks) { if self.account_creating { + let on_create = |m: &mut WalletAccountsModal| { + if m.account_label_edit.is_empty() { + return; + } + let label = &m.account_label_edit; + match wallet.create_account(label) { + Ok(_) => { + let _ = wallet.set_active_account(label); + modal.close(); + }, + Err(_) => m.account_creation_error = true + }; + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("wallets.new_account_desc")) @@ -74,11 +87,13 @@ impl WalletAccountsModal { ui.add_space(8.0); // Draw account name edit. - let text_edit_id = Id::from(modal.id).with(wallet.get_config().id); - let mut text_edit_opts = TextEditOptions::new(text_edit_id); - View::text_edit(ui, cb, &mut self.account_label_edit, &mut text_edit_opts); + let mut name_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id)); + name_edit.ui(ui, &mut self.account_label_edit, cb); + if name_edit.enter_pressed { + on_create(self); + } - // Show error occurred during account creation.. + // Show error occurred during account creation. if self.account_creation_error { ui.add_space(12.0); ui.label(RichText::new(t!("error")) @@ -100,25 +115,9 @@ impl WalletAccountsModal { }); }); columns[1].vertical_centered_justified(|ui| { - // Create button callback. - let mut on_create = || { - if !self.account_label_edit.is_empty() { - let label = &self.account_label_edit; - match wallet.create_account(label) { - Ok(_) => { - let _ = wallet.set_active_account(label); - modal.close(); - }, - Err(_) => self.account_creation_error = true - }; - } - }; - - View::on_enter_key(ui, || { - (on_create)(); + View::button(ui, t!("create"), Colors::white_or_black(false), || { + on_create(self); }); - - View::button(ui, t!("create"), Colors::white_or_black(false), on_create); }); }); ui.add_space(6.0); diff --git a/src/gui/views/wallets/wallet/settings/common.rs b/src/gui/views/wallets/wallet/settings/common.rs index 1dcd56ba..8be024b1 100644 --- a/src/gui/views/wallets/wallet/settings/common.rs +++ b/src/gui/views/wallets/wallet/settings/common.rs @@ -17,8 +17,8 @@ use egui::{Id, RichText}; use crate::gui::Colors; use crate::gui::icons::{CLOCK_COUNTDOWN, PASSWORD, PENCIL}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::{ModalPosition, TextEditOptions}; +use crate::gui::views::{Modal, TextEdit, View}; +use crate::gui::views::types::ModalPosition; use crate::wallet::Wallet; /// Common wallet settings content. @@ -26,8 +26,6 @@ pub struct CommonSettings { /// Wallet name [`Modal`] value. name_edit: String, - /// Flag to check if password change [`Modal`] was opened at first time to focus input field. - first_edit_pass_opening: bool, /// Flag to check if wrong password was entered. wrong_pass: bool, /// Current wallet password [`Modal`] value. @@ -50,7 +48,6 @@ impl Default for CommonSettings { fn default() -> Self { Self { name_edit: "".to_string(), - first_edit_pass_opening: true, wrong_pass: false, old_pass_edit: "".to_string(), new_pass_edit: "".to_string(), @@ -99,7 +96,6 @@ impl CommonSettings { let pass_text = format!("{} {}", PASSWORD, t!("change")); View::button(ui, pass_text, Colors::white_or_black(false), || { // Setup modal values. - self.first_edit_pass_opening = true; self.old_pass_edit = "".to_string(); self.new_pass_edit = "".to_string(); self.wrong_pass = false; @@ -176,17 +172,25 @@ impl CommonSettings { wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut CommonSettings| { + if !c.name_edit.is_empty() { + wallet.change_name(c.name_edit.clone()); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("wallets.name")) .size(17.0) .color(Colors::gray())); ui.add_space(8.0); - // Show wallet name text edit. - let name_edit_id = Id::from(modal.id).with(wallet.get_config().id); - let mut name_edit_opts = TextEditOptions::new(name_edit_id); - View::text_edit(ui, cb, &mut self.name_edit, &mut name_edit_opts); + let mut name_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id)); + name_edit.ui(ui, &mut self.name_edit, cb); + if name_edit.enter_pressed { + on_save(self); + } ui.add_space(12.0); }); @@ -203,19 +207,9 @@ impl CommonSettings { }); }); columns[1].vertical_centered_justified(|ui| { - // Save button callback. - let on_save = || { - if !self.name_edit.is_empty() { - wallet.change_name(self.name_edit.clone()); - modal.close(); - } - }; - - View::on_enter_key(ui, || { - (on_save)(); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); }); - - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); }); }); ui.add_space(6.0); @@ -229,6 +223,23 @@ impl CommonSettings { modal: &Modal, cb: &dyn PlatformCallbacks) { let wallet_id = wallet.get_config().id; + let on_continue = |c: &mut CommonSettings| { + if c.new_pass_edit.is_empty() { + return; + } + let old_pass = c.old_pass_edit.clone(); + let new_pass = c.new_pass_edit.clone(); + match wallet.change_password(old_pass, new_pass) { + Ok(_) => { + // Clear password values. + c.old_pass_edit = "".to_string(); + c.new_pass_edit = "".to_string(); + // Close modal. + modal.close(); + } + Err(_) => c.wrong_pass = true + } + }; ui.add_space(6.0); ui.vertical_centered(|ui| { @@ -239,12 +250,10 @@ impl CommonSettings { // Draw old password text edit. let pass_edit_id = Id::from(modal.id).with(wallet_id).with("old_pass"); - let mut pass_edit_opts = TextEditOptions::new(pass_edit_id).password().no_focus(); - if self.first_edit_pass_opening { - self.first_edit_pass_opening = false; - pass_edit_opts.focus = true; - } - View::text_edit(ui, cb, &mut self.old_pass_edit, &mut pass_edit_opts); + let mut pass_edit = TextEdit::new(pass_edit_id) + .password() + .focus(Modal::first_draw()); + pass_edit.ui(ui, &mut self.old_pass_edit, cb); ui.add_space(8.0); ui.label(RichText::new(t!("wallets.new_pass")) @@ -254,10 +263,16 @@ impl CommonSettings { // Draw new password text edit. let new_pass_edit_id = Id::from(modal.id).with(wallet_id).with("new_pass"); - let mut new_pass_edit_opts = TextEditOptions::new(new_pass_edit_id) + let mut new_pass_edit = TextEdit::new(new_pass_edit_id) .password() - .no_focus(); - View::text_edit(ui, cb, &mut self.new_pass_edit, &mut new_pass_edit_opts); + .focus(false); + if pass_edit.enter_pressed { + new_pass_edit.focus_request(); + } + new_pass_edit.ui(ui, &mut self.new_pass_edit, cb); + if new_pass_edit.enter_pressed { + on_continue(self); + } // Show information when password is empty. if self.old_pass_edit.is_empty() || self.new_pass_edit.is_empty() { @@ -287,31 +302,9 @@ impl CommonSettings { }); }); columns[1].vertical_centered_justified(|ui| { - // Callback for button to continue. - let mut on_continue = || { - if self.new_pass_edit.is_empty() { - return; - } - let old_pass = self.old_pass_edit.clone(); - let new_pass = self.new_pass_edit.clone(); - match wallet.change_password(old_pass, new_pass) { - Ok(_) => { - // Clear password values. - self.old_pass_edit = "".to_string(); - self.new_pass_edit = "".to_string(); - // Close modal. - modal.close(); - } - Err(_) => self.wrong_pass = true - } - }; - - // Continue on Enter key press. - View::on_enter_key(ui, || { - (on_continue)(); + View::button(ui, t!("change"), Colors::white_or_black(false), || { + on_continue(self); }); - - View::button(ui, t!("change"), Colors::white_or_black(false), on_continue); }); }); ui.add_space(6.0); @@ -324,6 +317,13 @@ impl CommonSettings { wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_save = |c: &mut CommonSettings| { + if let Ok(min_conf) = c.min_confirmations_edit.parse::() { + wallet.update_min_confirmations(min_conf); + modal.close(); + } + }; + ui.add_space(6.0); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("wallets.min_tx_conf_count")) @@ -332,8 +332,11 @@ impl CommonSettings { ui.add_space(8.0); // Minimum amount of confirmations text edit. - let mut text_edit_opts = TextEditOptions::new(Id::from(modal.id)).h_center(); - View::text_edit(ui, cb, &mut self.min_confirmations_edit, &mut text_edit_opts); + let mut min_confirmations_edit = TextEdit::new(Id::from(modal.id)).h_center(); + min_confirmations_edit.ui(ui, &mut self.min_confirmations_edit, cb); + if min_confirmations_edit.enter_pressed { + on_save(self); + } // Show error when specified value is not valid or reminder to restart enabled node. if self.min_confirmations_edit.parse::().is_err() { @@ -358,19 +361,9 @@ impl CommonSettings { }); }); columns[1].vertical_centered_justified(|ui| { - // Save button callback. - let on_save = || { - if let Ok(min_conf) = self.min_confirmations_edit.parse::() { - wallet.update_min_confirmations(min_conf); - modal.close(); - } - }; - - View::on_enter_key(ui, || { - (on_save)(); + View::button(ui, t!("modal.save"), Colors::white_or_black(false), || { + on_save(self); }); - - View::button(ui, t!("modal.save"), Colors::white_or_black(false), on_save); }); }); ui.add_space(6.0); diff --git a/src/gui/views/wallets/wallet/settings/recovery.rs b/src/gui/views/wallets/wallet/settings/recovery.rs index ea085278..e0eddd1c 100644 --- a/src/gui/views/wallets/wallet/settings/recovery.rs +++ b/src/gui/views/wallets/wallet/settings/recovery.rs @@ -19,8 +19,8 @@ use grin_util::ZeroingString; use crate::gui::Colors; use crate::gui::icons::{EYE, LIFEBUOY, STETHOSCOPE, TRASH, WRENCH}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Modal, View}; -use crate::gui::views::types::{ModalPosition, TextEditOptions}; +use crate::gui::views::{Modal, TextEdit, View}; +use crate::gui::views::types::ModalPosition; use crate::node::Node; use crate::wallet::types::ConnectionMethod; use crate::wallet::Wallet; @@ -182,6 +182,18 @@ impl RecoverySettings { wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) { + let on_next = |c: &mut RecoverySettings| { + match wallet.get_recovery(c.pass_edit.clone()) { + Ok(phrase) => { + c.wrong_pass = false; + c.recovery_phrase = Some(phrase); + } + Err(_) => { + c.wrong_pass = true; + } + } + }; + ui.add_space(6.0); if self.recovery_phrase.is_some() { ui.vertical_centered(|ui| { @@ -205,8 +217,12 @@ impl RecoverySettings { // Draw current wallet password text edit. let pass_edit_id = Id::from(modal.id).with(wallet.get_config().id); - let mut pass_edit_opts = TextEditOptions::new(pass_edit_id).password(); - View::text_edit(ui, cb, &mut self.pass_edit, &mut pass_edit_opts); + let mut pass_edit = TextEdit::new(pass_edit_id) + .password(); + pass_edit.ui(ui, &mut self.pass_edit, cb); + if pass_edit.enter_pressed { + on_next(self); + } // Show information when password is empty or wrong. if self.pass_edit.is_empty() { @@ -236,22 +252,8 @@ impl RecoverySettings { }); }); columns[1].vertical_centered_justified(|ui| { - let mut on_next = || { - match wallet.get_recovery(self.pass_edit.clone()) { - Ok(phrase) => { - self.wrong_pass = false; - self.recovery_phrase = Some(phrase); - } - Err(_) => { - self.wrong_pass = true; - } - } - }; - View::on_enter_key(ui, || { - (on_next)(); - }); View::button(ui, "OK".to_owned(), Colors::white_or_black(false), || { - on_next(); + on_next(self); }); }); }); diff --git a/src/gui/views/wallets/wallet/transport/send.rs b/src/gui/views/wallets/wallet/transport/send.rs index a172ee59..394f336b 100644 --- a/src/gui/views/wallets/wallet/transport/send.rs +++ b/src/gui/views/wallets/wallet/transport/send.rs @@ -21,17 +21,13 @@ use parking_lot::RwLock; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{CameraContent, Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{CameraContent, Modal, TextEdit, View}; use crate::gui::views::wallets::wallet::WalletTransactionModal; use crate::wallet::types::WalletTransaction; use crate::wallet::Wallet; /// Transport sending [`Modal`] content. pub struct TransportSendModal { - /// Flag to focus on first input field after opening. - first_draw: bool, - /// Flag to check if transaction is sending to show progress. sending: bool, /// Flag to check if there is an error to repeat. @@ -57,7 +53,6 @@ impl TransportSendModal { /// Create new instance from provided address. pub fn new(addr: Option) -> Self { Self { - first_draw: true, sending: false, error: false, send_result: Arc::new(RwLock::new(None)), @@ -100,8 +95,7 @@ impl TransportSendModal { ui.add_space(6.0); // Draw QR code scanner content if requested. if let Some(scanner) = self.address_scan_content.as_mut() { - let mut on_stop = || { - self.first_draw = true; + let on_stop = || { cb.stop_camera(); modal.enable_closing(); }; @@ -150,13 +144,11 @@ impl TransportSendModal { // Draw amount text edit. let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id); - let mut amount_edit_opts = TextEditOptions::new(amount_edit_id).h_center().no_focus(); + let mut amount_edit = TextEdit::new(amount_edit_id) + .h_center() + .focus(Modal::first_draw()); let amount_edit_before = self.amount_edit.clone(); - if self.first_draw { - self.first_draw = false; - amount_edit_opts.focus = true; - } - View::text_edit(ui, cb, &mut self.amount_edit, &mut amount_edit_opts); + amount_edit.ui(ui, &mut self.amount_edit, cb); ui.add_space(8.0); // Check value if input was changed. @@ -173,7 +165,7 @@ impl TransportSendModal { return; } } else { - // Check input after ".". + // Check input after `.`. let parts = self.amount_edit.split(".").collect::>(); if parts.len() == 2 && parts[1].len() > 9 { self.amount_edit = amount_edit_before; @@ -211,15 +203,17 @@ impl TransportSendModal { // Draw address text edit. let addr_edit_before = self.address_edit.clone(); let address_edit_id = Id::from(modal.id).with("_address").with(wallet.get_config().id); - let mut address_edit_opts = TextEditOptions::new(address_edit_id) + let mut address_edit = TextEdit::new(address_edit_id) .paste() - .no_focus() + .focus(false) .scan_qr(); - View::text_edit(ui, cb, &mut self.address_edit, &mut address_edit_opts); + if amount_edit.enter_pressed { + address_edit.focus_request(); + } + address_edit.ui(ui, &mut self.address_edit, cb); // Check if scan button was pressed. - if address_edit_opts.scan_pressed { + if address_edit.scan_pressed { modal.disable_closing(); - address_edit_opts.scan_pressed = false; self.address_scan_content = Some(CameraContent::default()); } ui.add_space(12.0); @@ -229,6 +223,11 @@ impl TransportSendModal { self.address_error = false; } + // Send on Enter press. + if address_edit.enter_pressed { + self.send(wallet, modal); + } + // Setup spacing between buttons. ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); diff --git a/src/gui/views/wallets/wallet/transport/settings.rs b/src/gui/views/wallets/wallet/transport/settings.rs index ebf7ae1c..91803451 100644 --- a/src/gui/views/wallets/wallet/transport/settings.rs +++ b/src/gui/views/wallets/wallet/transport/settings.rs @@ -17,8 +17,7 @@ use egui::{Id, RichText}; use crate::gui::Colors; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{CameraContent, Modal, View}; -use crate::gui::views::types::TextEditOptions; +use crate::gui::views::{CameraContent, Modal, TextEdit, View}; use crate::tor::{Tor, TorBridge, TorConfig}; use crate::wallet::Wallet; @@ -160,16 +159,16 @@ impl TransportSettingsModal { let bin_edit_id = Id::from(modal.id) .with(wallet.get_config().id) .with("_bin_edit"); - let mut bin_edit_opts = TextEditOptions::new(bin_edit_id) + let mut bin_edit = TextEdit::new(bin_edit_id) .paste() - .no_focus(); + .focus(false); let bin_edit_before = self.bridge_bin_path_edit.clone(); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("transport.bin_file")) .size(17.0) .color(Colors::inactive_text())); ui.add_space(6.0); - View::text_edit(ui, cb, &mut self.bridge_bin_path_edit, &mut bin_edit_opts); + bin_edit.ui(ui, &mut self.bridge_bin_path_edit, cb); ui.add_space(6.0); }); @@ -178,20 +177,19 @@ impl TransportSettingsModal { let conn_edit_id = Id::from(modal.id) .with(wallet.get_config().id) .with("_conn_edit"); - let mut conn_edit_opts = TextEditOptions::new(conn_edit_id) + let mut conn_edit = TextEdit::new(conn_edit_id) .paste() - .no_focus() + .focus(false) .scan_qr(); ui.vertical_centered(|ui| { ui.label(RichText::new(t!("transport.conn_line")) .size(17.0) .color(Colors::inactive_text())); ui.add_space(6.0); - View::text_edit(ui, cb, &mut self.bridge_conn_line_edit, &mut conn_edit_opts); + conn_edit.ui(ui, &mut self.bridge_conn_line_edit, cb); // Check if scan button was pressed. - if conn_edit_opts.scan_pressed { + if conn_edit.scan_pressed { modal.disable_closing(); - conn_edit_opts.scan_pressed = false; self.bridge_qr_scan_content = Some(CameraContent::default()); } }); diff --git a/src/settings/config.rs b/src/settings/config.rs index e4db6408..0b6ef007 100644 --- a/src/settings/config.rs +++ b/src/settings/config.rs @@ -46,6 +46,8 @@ pub struct AppConfig { /// Locale code for i18n. lang: Option, + /// Flag to use English locale layout on keyboard. + english_keyboard: Option, /// Flag to check if dark theme should be used, use system settings if not set. use_dark_theme: Option, @@ -64,6 +66,7 @@ impl Default for AppConfig { x: None, y: None, lang: None, + english_keyboard: None, use_dark_theme: None, } } @@ -212,6 +215,20 @@ impl AppConfig { None } + /// Toggle English locale layout. for software keyboard. + pub fn toggle_english_keyboard() { + let english = Self::english_keyboard(); + let mut w_app_config = Settings::app_config_to_update(); + w_app_config.english_keyboard = Some(!english); + w_app_config.save(); + } + + /// Check if English locale layout should be used for software keyboard. + pub fn english_keyboard() -> bool { + let r_config = Settings::app_config_to_read(); + r_config.english_keyboard.unwrap_or(false) + } + /// Check if integrated node warning is needed for Android. pub fn android_integrated_node_warning_needed() -> bool { let r_config = Settings::app_config_to_read();