ui: input refactoring

This commit is contained in:
ardocrat
2025-05-26 20:48:29 +03:00
parent d11e90226b
commit 2591653f66
25 changed files with 1172 additions and 1077 deletions
+1 -1
View File
@@ -139,7 +139,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
}
// 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() {
+307
View File
@@ -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
}
}
@@ -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<RwLock<Option<KeyboardInput>>> = Arc::new(RwLock::new(None));
/// Flag to show software keyboard.
/// Last input event.
static ref LAST_EVENT: Arc<RwLock<Option<KeyboardEvent >>> = 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<Color32>,
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<KeyboardInput> {
/// Check last keyboard input event.
pub fn consume_event() -> Option<KeyboardEvent> {
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);
+22
View File
@@ -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::*;
+25
View File
@@ -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
}
+2 -2
View File
@@ -45,5 +45,5 @@ pub use pull_to_refresh::*;
mod scan;
pub use scan::*;
mod keyboard;
pub use keyboard::*;
mod input;
pub use input::*;
+20 -2
View File
@@ -40,6 +40,8 @@ pub struct Modal {
closeable: Arc<AtomicBool>,
/// Title text.
title: Option<String>,
/// Flag to check first content render.
first_draw: Arc<AtomicBool>,
}
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`].
+56 -60
View File
@@ -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<i64>,
}
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);
+68 -68
View File
@@ -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::<u16>() {
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::<u16>().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::<u16>() {
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::<u16>() {
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::<u16>().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::<u16>() {
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::<u16>() {
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::<u16>().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::<u16>() {
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::<u8>() {
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::<u8>().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::<u8>() {
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);
+69 -71
View File
@@ -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::<u64>() {
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::<u64>().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::<u64>() {
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);
+100 -107
View File
@@ -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::<u16>().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::<u16>().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::<i64>() {
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::<i64>().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::<i64>() {
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::<u32>() {
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::<u32>().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::<u32>() {
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::<u32>() {
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::<u32>().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::<u32>() {
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);
+76 -82
View File
@@ -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::<u64>() {
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::<u64>().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::<u64>() {
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::<u32>() {
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::<u32>().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::<u32>() {
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::<usize>() {
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::<usize>().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::<usize>() {
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::<usize>() {
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::<usize>().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::<usize>() {
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::<u64>() {
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::<u64>().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::<u64>() {
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);
+58 -61
View File
@@ -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::<u32>() {
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::<u32>().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::<u32>() {
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::<u64>() {
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::<u64>().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::<u64>() {
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);
-75
View File
@@ -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 {
+7 -173
View File
@@ -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 {
+30 -31
View File
@@ -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);
+26 -44
View File
@@ -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);
+25 -32
View File
@@ -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<String>)) {
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);
@@ -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 {
+23 -24
View File
@@ -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);
+62 -69
View File
@@ -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::<u64>() {
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::<u64>().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::<u64>() {
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);
@@ -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);
});
});
});
+19 -20
View File
@@ -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<String>) -> 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::<Vec<&str>>();
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);
@@ -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());
}
});
+17
View File
@@ -46,6 +46,8 @@ pub struct AppConfig {
/// Locale code for i18n.
lang: Option<String>,
/// Flag to use English locale layout on keyboard.
english_keyboard: Option<bool>,
/// Flag to check if dark theme should be used, use system settings if not set.
use_dark_theme: Option<bool>,
@@ -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();