ui: numeric keyboard input

This commit is contained in:
ardocrat
2025-05-27 14:28:23 +03:00
parent 3f78095fe3
commit 1987d0553c
10 changed files with 250 additions and 166 deletions
+155 -73
View File
@@ -39,13 +39,13 @@ lazy_static! {
/// Software keyboard content.
pub struct KeyboardContent {
/// Keyboard layout.
mode: KeyboardLayout
layout: KeyboardLayout
}
impl Default for KeyboardContent {
fn default() -> Self {
Self {
mode: KeyboardLayout::TEXT,
layout: KeyboardLayout::TEXT,
}
}
}
@@ -53,10 +53,13 @@ impl Default for KeyboardContent {
impl KeyboardContent {
/// Maximum keyboard content width.
const MAX_WIDTH: f32 = 600.0;
/// Maximum numbers layout width.
const MAX_WIDTH_NUMBERS: f32 = 400.0;
/// Draw keyboard content as separate [`Window`].
pub fn window_ui(&mut self, ctx: &egui::Context) {
if !KeyboardContent::window_showing() {
self.layout = KeyboardLayout::TEXT;
return;
}
let width = ctx.screen_rect().width();
@@ -78,7 +81,7 @@ impl KeyboardContent {
left: View::get_left_inset(),
right: View::get_right_inset(),
top: 1.0,
bottom: View::get_bottom_inset() + 1.0,
bottom: View::get_bottom_inset(),
},
fill: Colors::fill(),
..Default::default()
@@ -88,7 +91,12 @@ impl KeyboardContent {
// Calculate content width.
let side_insets = View::get_left_inset() + View::get_right_inset();
let available_width = width - side_insets;
let w = f32::min(available_width, Self::MAX_WIDTH);
let w = f32::min(available_width, if self.layout == KeyboardLayout::NUMBERS {
Self::MAX_WIDTH_NUMBERS
} else {
Self::MAX_WIDTH
});
// Draw content.
View::max_width_ui(ui, w, |ui| {
self.ui(ui);
});
@@ -105,9 +113,17 @@ impl KeyboardContent {
// Setup vertical padding inside buttons.
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 8.0);
let button_rect = match self.mode {
// Set numbers layout type if passed on opening.
if NUMERIC.load(Ordering::Relaxed) {
self.layout = KeyboardLayout::NUMBERS;
} else if self.layout == KeyboardLayout::NUMBERS {
self.layout = KeyboardLayout::TEXT;
}
let button_rect = match self.layout {
KeyboardLayout::TEXT => Self::text_ui(ui),
KeyboardLayout::SYMBOLS => Self::symbols_ui(ui),
KeyboardLayout::NUMBERS => Self::numbers_ui(ui),
};
ui.add_space(2.0);
@@ -117,93 +133,159 @@ impl KeyboardContent {
r.set_width(ui.available_width());
r.size()
};
let button_width = ui.available_width() / if self.mode != KeyboardLayout::SYMBOLS {
11.0
} else {
10.0
let button_width = ui.available_width() / match self.layout {
KeyboardLayout::TEXT => 11.0,
KeyboardLayout::SYMBOLS => 10.0,
KeyboardLayout::NUMBERS => 4.0,
};
ui.allocate_ui_with_layout(bottom_size, Layout::right_to_left(Align::Center), |ui| {
// Enter key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width * 2.0 + 1.0);
Self::custom_button_ui(KEY_RETURN.to_string(),
Colors::white_or_black(false),
Some(Colors::green()),
ui,
|_| {
Self::input_event(KeyboardEvent::ENTER);
});
});
// Backspace key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width * 1.0);
Self::custom_button_ui(BACKSPACE.to_string(),
Colors::red(),
Some(Colors::fill_lite()),
ui,
|_| {
Self::input_event(KeyboardEvent::CLEAR);
});
});
// Space key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width * 4.0);
Self::custom_button_ui(" ".to_string(), Colors::inactive_text(), None, ui, |l| {
Self::input_event(KeyboardEvent::TEXT(l));
if self.layout == KeyboardLayout::NUMBERS {
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width * 2.0 + 1.0);
Self::custom_button_ui(KEY_RETURN.to_string(),
Colors::white_or_black(false),
Some(Colors::green()),
ui,
|_| {
Self::input_event(KeyboardEvent::ENTER);
});
});
});
if self.mode != KeyboardLayout::SYMBOLS {
// Switch to english and back.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width);
Self::custom_button_ui(GLOBE_SIMPLE.to_string(),
Colors::text_button(),
Self::input_button_ui("0", true, ui);
});
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width);
Self::input_button_ui(".", false, ui);
});
} else {
// Enter key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width * 2.0 + 1.0);
Self::custom_button_ui(KEY_RETURN.to_string(),
Colors::white_or_black(false),
Some(Colors::green()),
ui,
|_| {
Self::input_event(KeyboardEvent::ENTER);
});
});
// Backspace key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width * 1.0);
Self::custom_button_ui(BACKSPACE.to_string(),
Colors::red(),
Some(Colors::fill_lite()),
ui,
|_| {
AppConfig::toggle_english_keyboard()
Self::input_event(KeyboardEvent::CLEAR);
});
});
// Shift key input.
// Space key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width);
let uppercase = UPPERCASE.load(Ordering::Relaxed);
let color = if uppercase {
Colors::yellow_dark()
ui.set_max_width(button_width * 4.0);
Self::custom_button_ui(" ".to_string(), Colors::inactive_text(), None, ui, |l| {
Self::input_event(KeyboardEvent::TEXT(l));
});
});
if self.layout == KeyboardLayout::TEXT {
// Switch to english and back.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width);
Self::custom_button_ui(GLOBE_SIMPLE.to_string(),
Colors::text_button(),
Some(Colors::fill_lite()),
ui,
|_| {
AppConfig::toggle_english_keyboard()
});
});
// Shift key input.
ui.horizontal_centered(|ui| {
ui.set_max_width(button_width);
let uppercase = UPPERCASE.load(Ordering::Relaxed);
let color = if uppercase {
Colors::yellow_dark()
} else {
Colors::inactive_text()
};
Self::custom_button_ui(ARROW_FAT_UP.to_string(),
color,
Some(Colors::fill_lite()),
ui, |_| {
UPPERCASE.store(!uppercase, Ordering::Relaxed);
});
});
}
// Switch to symbols and back.
ui.horizontal_centered(|ui| {
let label = if self.layout == KeyboardLayout::SYMBOLS {
let q = t!("keyboard.q", locale = Self::locale().as_str());
let w = t!("keyboard.w", locale = Self::locale().as_str());
let e = t!("keyboard.e", locale = Self::locale().as_str());
format!("{}{}{}", q, w, e).to_uppercase()
} else {
Colors::inactive_text()
"!@ツ".to_string()
};
Self::custom_button_ui(ARROW_FAT_UP.to_string(),
color,
let mut mode = self.layout.clone();
Self::custom_button_ui(label,
Colors::text(false),
Some(Colors::fill_lite()),
ui, |_| {
UPPERCASE.store(!uppercase, Ordering::Relaxed);
});
ui,
|_| {
if self.layout == KeyboardLayout::SYMBOLS {
mode = KeyboardLayout::TEXT;
} else {
mode = KeyboardLayout::SYMBOLS;
}
});
self.layout = mode;
});
}
// Switch to symbols and back.
ui.horizontal_centered(|ui| {
let label = if self.mode == KeyboardLayout::SYMBOLS {
let q = t!("keyboard.q", locale = Self::locale().as_str());
let w = t!("keyboard.w", locale = Self::locale().as_str());
let e = t!("keyboard.e", locale = Self::locale().as_str());
format!("{}{}{}", q, w, e).to_uppercase()
} else {
"?/ツ".to_string()
};
let mut mode = self.mode.clone();
Self::custom_button_ui(label, Colors::text(false), Some(Colors::fill_lite()), ui, |_| {
if self.mode == KeyboardLayout::SYMBOLS {
mode = KeyboardLayout::TEXT;
} else {
mode = KeyboardLayout::SYMBOLS;
}
});
self.mode = mode;
});
});
}
/// Draw numbers content returning button [`Rect`].
fn numbers_ui(ui: &mut egui::Ui) -> Rect {
let mut button_rect = ui.available_rect_before_wrap();
let tl_0: Vec<&str> = vec!["1", "2", "3", "-"];
ui.columns(tl_0.len(), |columns| {
for (index, s) in tl_0.iter().enumerate() {
let last = index == tl_0.len() - 1;
button_rect = Self::input_button_ui(s, !last, &mut columns[index]);
}
});
ui.add_space(2.0);
let tl_1: Vec<&str> = vec!["4", "5", "6", "+"];
ui.columns(tl_1.len(), |columns| {
for (index, s) in tl_1.iter().enumerate() {
let last = index == tl_1.len() - 1;
Self::input_button_ui(s, !last, &mut columns[index]);
}
});
ui.add_space(2.0);
let tl_2: Vec<&str> = vec!["7", "8", "9", BACKSPACE];
ui.columns(tl_2.len(), |columns| {
for (index, s) in tl_2.iter().enumerate() {
if index == tl_2.len() - 1 {
Self::custom_button_ui(BACKSPACE.to_string(),
Colors::red(),
Some(Colors::fill_lite()),
&mut columns[index],
|_| {
Self::input_event(KeyboardEvent::CLEAR);
});
} else {
Self::input_button_ui(s, true, &mut columns[index]);
}
}
});
button_rect
}
/// Draw text content returning button [`Rect`].
fn text_ui(ui: &mut egui::Ui) -> Rect {
let mut button_rect = ui.available_rect_before_wrap();
+1 -1
View File
@@ -15,7 +15,7 @@
/// Software keyboard input type.
#[derive(Clone, PartialOrd, PartialEq)]
pub enum KeyboardLayout {
TEXT, SYMBOLS
TEXT, SYMBOLS, NUMBERS
}
/// Software keyboard input event.
+4 -4
View File
@@ -171,7 +171,7 @@ impl DandelionSetup {
ui.add_space(8.0);
// Draw epoch text edit.
let mut epoch_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut epoch_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
epoch_edit.ui(ui, &mut self.epoch_edit, cb);
if epoch_edit.enter_pressed {
on_save(self);
@@ -249,7 +249,7 @@ impl DandelionSetup {
ui.add_space(8.0);
// Draw embargo text edit.
let mut embargo_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut embargo_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
embargo_edit.ui(ui, &mut self.embargo_edit, cb);
if embargo_edit.enter_pressed {
on_save(self);
@@ -328,7 +328,7 @@ impl DandelionSetup {
ui.add_space(8.0);
// Draw aggregation period text edit.
let mut aggregation_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut aggregation_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
aggregation_edit.ui(ui, &mut self.aggregation_edit, cb);
if aggregation_edit.enter_pressed {
on_save(self);
@@ -407,7 +407,7 @@ impl DandelionSetup {
ui.add_space(8.0);
// Draw stem phase probability text edit.
let mut stem_prob_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut stem_prob_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
stem_prob_edit.ui(ui, &mut self.stem_prob_edit, cb);
if stem_prob_edit.enter_pressed {
on_save(self);
+2 -2
View File
@@ -304,7 +304,7 @@ impl NodeSetup {
ui.add_space(6.0);
// Draw API port text edit.
let mut api_port_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut api_port_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
api_port_edit.ui(ui, &mut self.api_port_edit, cb);
if api_port_edit.enter_pressed {
on_save(self);
@@ -489,7 +489,7 @@ impl NodeSetup {
ui.add_space(8.0);
// Draw ftl value text edit.
let mut ftl_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut ftl_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
ftl_edit.ui(ui, &mut self.ftl_edit, cb);
if ftl_edit.enter_pressed {
on_save(self);
+4 -4
View File
@@ -296,7 +296,7 @@ impl P2PSetup {
ui.add_space(8.0);
// Draw p2p port text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.port_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -546,7 +546,7 @@ impl P2PSetup {
ui.add_space(8.0);
// Draw ban window text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.ban_window_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -625,7 +625,7 @@ impl P2PSetup {
ui.add_space(8.0);
// Draw maximum number of inbound peers text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.max_inbound_count, cb);
if edit.enter_pressed {
on_save(self);
@@ -703,7 +703,7 @@ impl P2PSetup {
ui.add_space(8.0);
// Draw maximum number of outbound peers text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.max_outbound_count, cb);
if edit.enter_pressed {
on_save(self);
+9 -8
View File
@@ -174,9 +174,11 @@ impl PoolSetup {
ui.add_space(8.0);
// Draw fee base text edit.
TextEdit::new(Id::from(modal.id))
.h_center()
.ui(ui, &mut self.fee_base_edit, cb);
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.fee_base_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.fee_base_edit.parse::<u64>().is_err() {
@@ -251,7 +253,7 @@ impl PoolSetup {
ui.add_space(8.0);
// Draw reorg period text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.reorg_period_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -329,8 +331,7 @@ impl PoolSetup {
ui.add_space(8.0);
// Draw pool size text edit.
let mut edit = TextEdit::new(Id::from(modal.id))
.h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.pool_size_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -410,7 +411,7 @@ impl PoolSetup {
ui.add_space(8.0);
// Draw stempool size text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.stempool_size_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -490,7 +491,7 @@ impl PoolSetup {
ui.add_space(8.0);
// Draw tx weight text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.max_weight_edit, cb);
if edit.enter_pressed {
on_save(self);
+3 -3
View File
@@ -312,7 +312,7 @@ impl StratumSetup {
ui.add_space(8.0);
// Draw stratum port text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.stratum_port_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -397,7 +397,7 @@ impl StratumSetup {
ui.add_space(8.0);
// Draw attempt time text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.attempt_time_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -476,7 +476,7 @@ impl StratumSetup {
ui.add_space(8.0);
// Draw share difficulty text edit.
let mut edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
edit.ui(ui, &mut self.min_share_diff_edit, cb);
if edit.enter_pressed {
on_save(self);
@@ -70,6 +70,38 @@ impl MessageRequestModal {
return;
}
// Setup callback on continue.
let on_continue = |m: &mut MessageRequestModal| {
if m.amount_edit.is_empty() {
return;
}
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
modal.disable_closing();
// Setup data for request.
let wallet = wallet.clone();
let invoice = m.invoice.clone();
let result = m.request_result.clone();
// Send request at another thread.
m.request_loading = true;
thread::spawn(move || {
let res = if invoice {
wallet.issue_invoice(a)
} else {
wallet.send(a, None)
};
let mut w_result = result.write();
*w_result = Some(res);
});
} else {
let err = if m.invoice {
t!("wallets.invoice_slatepack_err")
} else {
t!("wallets.send_slatepack_err")
};
m.request_error = Some(err);
}
};
ui.add_space(6.0);
// Draw content on request loading.
@@ -79,74 +111,6 @@ impl MessageRequestModal {
}
// Draw amount input content.
self.amount_input_ui(ui, wallet, modal, cb);
// Show request creation error.
if let Some(err) = &self.request_error {
ui.add_space(12.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(err)
.size(17.0)
.color(Colors::red()));
});
}
ui.add_space(12.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
self.amount_edit = "".to_string();
self.request_error = None;
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
// Button to create Slatepack message request.
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
if self.amount_edit.is_empty() {
return;
}
if let Ok(a) = amount_from_hr_string(self.amount_edit.as_str()) {
modal.disable_closing();
// Setup data for request.
let wallet = wallet.clone();
let invoice = self.invoice.clone();
let result = self.request_result.clone();
// Send request at another thread.
self.request_loading = true;
thread::spawn(move || {
let res = if invoice {
wallet.issue_invoice(a)
} else {
wallet.send(a, None)
};
let mut w_result = result.write();
*w_result = Some(res);
});
} else {
let err = if self.invoice {
t!("wallets.invoice_slatepack_err")
} else {
t!("wallets.send_slatepack_err")
};
self.request_error = Some(err);
}
});
});
});
ui.add_space(6.0);
}
/// Draw amount input content.
fn amount_input_ui(&mut self,
ui: &mut egui::Ui,
wallet: &Wallet,
modal: &Modal,
cb: &dyn PlatformCallbacks) {
ui.vertical_centered(|ui| {
let enter_text = if self.invoice {
t!("wallets.enter_amount_receive")
@@ -162,10 +126,14 @@ impl MessageRequestModal {
ui.add_space(8.0);
// Draw request amount text input.
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();
let mut amount_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id))
.h_center()
.numeric();
amount_edit.ui(ui, &mut self.amount_edit, cb);
if amount_edit.enter_pressed {
on_continue(self);
}
// Check value if input was changed.
if amount_edit_before != self.amount_edit {
@@ -205,6 +173,38 @@ impl MessageRequestModal {
}
}
}
// Show request creation error.
if let Some(err) = &self.request_error {
ui.add_space(12.0);
ui.vertical_centered(|ui| {
ui.label(RichText::new(err)
.size(17.0)
.color(Colors::red()));
});
}
ui.add_space(12.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
self.amount_edit = "".to_string();
self.request_error = None;
modal.close();
});
});
columns[1].vertical_centered_justified(|ui| {
// Button to create Slatepack message request.
View::button(ui, t!("continue"), Colors::white_or_black(false), || {
on_continue(self);
});
});
});
ui.add_space(6.0);
}
/// Draw loading request content.
@@ -332,7 +332,7 @@ impl CommonSettings {
ui.add_space(8.0);
// Minimum amount of confirmations text edit.
let mut min_confirmations_edit = TextEdit::new(Id::from(modal.id)).h_center();
let mut min_confirmations_edit = TextEdit::new(Id::from(modal.id)).h_center().numeric();
min_confirmations_edit.ui(ui, &mut self.min_confirmations_edit, cb);
if min_confirmations_edit.enter_pressed {
on_save(self);
@@ -146,6 +146,7 @@ impl TransportSendModal {
let amount_edit_id = Id::from(modal.id).with("amount").with(wallet.get_config().id);
let mut amount_edit = TextEdit::new(amount_edit_id)
.h_center()
.numeric()
.focus(Modal::first_draw());
let amount_edit_before = self.amount_edit.clone();
amount_edit.ui(ui, &mut self.amount_edit, cb);