1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 23:48:12 +00:00

feat(core/ui): T3T1 recovery keyboards

[no changelog]
This commit is contained in:
obrusvit 2024-05-20 21:46:53 +02:00 committed by Martin Milata
parent 3f0ab537af
commit e5e8e27abc
6 changed files with 232 additions and 235 deletions

View File

@ -2,16 +2,16 @@ use crate::{
trezorhal::bip39,
ui::{
component::{text::common::TextBox, Component, Event, EventCtx},
display,
geometry::{Alignment2D, Offset, Rect},
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
model_mercury::{
component::{
keyboard::{
common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard},
common::{render_pending_marker, render_pill_shape, MultiTapKeyboard},
mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT},
},
Button, ButtonContent, ButtonMsg,
Button, ButtonMsg,
},
constant::WIDTH,
theme,
},
shape,
@ -104,100 +104,66 @@ impl Component for Bip39Input {
}
fn paint(&mut self) {
let area = self.button.area();
let style = self.button.style();
// First, paint the button background.
self.button.paint_background(style);
// Paint the entered content (the prefix of the suggested word).
let text = self.textbox.content();
let width = style.font.text_width(text);
// Content starts in the left-center point, offset by 16px to the right and 8px
// to the bottom.
let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8);
display::text_left(
text_baseline,
text,
style.font,
style.text_color,
style.button_color,
);
// Paint the rest of the suggested dictionary word.
if let Some(word) = self.suggested_word.and_then(|w| w.get(text.len()..)) {
let word_baseline = text_baseline + Offset::new(width, 0);
let style = self.button_suggestion.style();
display::text_left(
word_baseline,
word,
style.font,
style.text_color,
style.button_color,
);
}
// Paint the pending marker.
if self.multi_tap.pending_key().is_some() {
paint_pending_marker(text_baseline, text, style.font, style.text_color);
}
// Paint the icon.
if let ButtonContent::Icon(icon) = self.button.content() {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge.
let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0);
icon.draw(
icon_center,
Alignment2D::CENTER,
style.text_color,
style.button_color,
);
}
todo!("remove when ui-t3t1 done");
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let area = self.button.area();
let style = self.button.style();
// First, paint the button background.
self.button.render_background(target, style);
// Paint the entered content (the prefix of the suggested word).
let text = self.textbox.content();
let width = style.font.text_width(text);
// Content starts in the left-center point, offset by 16px to the right and 8px
// to the bottom.
let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8);
shape::Text::new(text_baseline, text)
// User input together with suggestion is centered vertically in the input area
// and centered horizontally on the screen
let text_base_y = area.left_center().y + style.font.allcase_text_height() / 2;
let text_base_x = if let Some(word) = self.suggested_word {
style.font.horz_center(0, WIDTH, word)
} else {
style.font.horz_center(0, WIDTH, text)
};
let text_base = Point::new(text_base_x, text_base_y);
// Render pill-shaped button
if let Some(word) = self.suggested_word {
let choice_unambiguous = self.is_choice_unambiguous();
render_pill_shape(
target,
text_base,
word,
style,
if choice_unambiguous { Some(area) } else { None },
);
if choice_unambiguous {
// Icon is painted in the right-center point, 10px from the right edge.
let icon_right_center = area.right_center() - Offset::x(10);
shape::ToifImage::new(icon_right_center, theme::ICON_SIMPLE_CHECKMARK24.toif)
.with_align(Alignment2D::CENTER_RIGHT)
.with_fg(style.icon_color)
.render(target);
}
};
// Render text input + suggested completion
shape::Text::new(text_base, text)
.with_font(style.font)
.with_fg(style.text_color)
.with_align(Alignment::Start)
.render(target);
// Paint the rest of the suggested dictionary word.
if let Some(word) = self.suggested_word.and_then(|w| w.get(text.len()..)) {
let word_baseline = text_baseline + Offset::new(width, 0);
let word_baseline = text_base + Offset::x(width);
let style = self.button_suggestion.style();
shape::Text::new(word_baseline, word)
.with_font(style.font)
.with_fg(style.text_color)
.with_align(Alignment::Start)
.render(target);
}
// Paint the pending marker.
if self.multi_tap.pending_key().is_some() {
render_pending_marker(target, text_baseline, text, style.font, style.text_color);
}
// Paint the icon.
if let ButtonContent::Icon(icon) = self.button.content() {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge.
let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0);
shape::ToifImage::new(icon_center, icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(style.icon_color)
.render(target);
render_pending_marker(target, text_base, text, style.font, style.text_color);
}
}
@ -227,8 +193,7 @@ impl Bip39Input {
// Styling the input to reflect already filled word
Self {
button: Button::with_icon(theme::ICON_CONFIRM_INPUT)
.styled(theme::button_pin_confirm()),
button: Button::empty().styled(theme::button_recovery_confirm()),
textbox: TextBox::new(unwrap!(String::try_from(word))),
multi_tap: MultiTapKeyboard::new(),
options_num: bip39::options_num(word),
@ -249,13 +214,18 @@ impl Bip39Input {
mask
}
fn is_choice_unambiguous(&self) -> bool {
if let (Some(word), Some(_num)) = (self.suggested_word, self.options_num) {
return word.eq(self.textbox.content());
}
false
}
/// Input button was clicked. If the content matches the suggested word,
/// let's confirm it, otherwise just auto-complete.
fn on_input_click(&mut self, ctx: &mut EventCtx) -> Option<MnemonicInputMsg> {
if let (Some(word), Some(num)) = (self.suggested_word, self.options_num) {
return if num == 1 && word.starts_with(self.textbox.content())
|| num > 1 && word.eq(self.textbox.content())
{
if let (Some(word), Some(_num)) = (self.suggested_word, self.options_num) {
return if word.eq(self.textbox.content()) {
// Confirm button.
self.textbox.clear(ctx);
Some(MnemonicInputMsg::Confirmed)
@ -286,24 +256,19 @@ impl Bip39Input {
self.suggested_word = bip39::complete_word(self.textbox.content());
// Change the style of the button depending on the completed word.
if let (Some(word), Some(num)) = (self.suggested_word, self.options_num) {
if num == 1 && word.starts_with(self.textbox.content())
|| num > 1 && word.eq(self.textbox.content())
{
if let (Some(word), Some(_num)) = (self.suggested_word, self.options_num) {
if word.eq(self.textbox.content()) {
// Confirm button.
self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_pin_confirm());
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CONFIRM_INPUT));
.set_stylesheet(ctx, theme::button_recovery_confirm());
self.button_suggestion
.set_stylesheet(ctx, theme::button_suggestion_confirm());
} else {
// Auto-complete button.
self.button.enable(ctx);
self.button
.set_stylesheet(ctx, theme::button_bip39_autocomplete());
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_AUTOFILL));
.set_stylesheet(ctx, theme::button_recovery_autocomplete());
self.button_suggestion
.set_stylesheet(ctx, theme::button_suggestion_autocomplete());
}
@ -311,7 +276,6 @@ impl Bip39Input {
// Disabled button.
self.button.disable(ctx);
self.button.set_stylesheet(ctx, theme::button_keyboard());
self.button.set_content(ctx, ButtonContent::Text("".into()));
}
}
}

View File

@ -3,12 +3,14 @@ use crate::{
ui::{
component::{text::common::TextEdit, Event, EventCtx, TimerToken},
display::{self, Color, Font},
geometry::{Offset, Point, Rect},
geometry::{Alignment2D, Offset, Point, Rect},
shape,
shape::Renderer,
},
};
use super::super::ButtonStyle;
/// Contains state commonly used in implementations multi-tap keyboards.
pub struct MultiTapKeyboard {
/// Configured timeout after which we cancel currently pending key.
@ -150,3 +152,41 @@ pub fn render_pending_marker<'s>(
shape::Bar::new(marker_rect).with_bg(color).render(target);
}
}
/// Create a pill-shaped button around a text.
pub fn render_pill_shape<'s>(
target: &mut impl Renderer<'s>,
base_point: Point,
text: &str,
style: &ButtonStyle,
expand_area: Option<Rect>,
) {
let pill_radius = 18;
let pill_bearing_x = 17;
let pill_bearing_y = 12;
let pill_height = 2 * pill_radius + 4; // adding 4px looks better, if the height is just 2*r it does not look like a
// perfect half-circle but there is a visible narrowing of the pill shape
let pill_width = style.font.text_width(text) + 2 * pill_bearing_x;
let pill_baseline = base_point + Offset::new(-pill_bearing_x, pill_bearing_y);
let mut pill_area = Rect::snap(
pill_baseline,
Offset::new(pill_width, pill_height),
Alignment2D::BOTTOM_LEFT,
);
if let Some(area) = expand_area {
// "dummy" rectangle to use in the `union` call
let expander = Rect::snap(
area.bottom_right(),
Offset::uniform(1),
Alignment2D::BOTTOM_RIGHT,
);
pill_area = pill_area.union(expander);
}
shape::Bar::new(pill_area)
.with_bg(style.background_color)
.with_fg(style.button_color)
.with_radius(pill_radius)
.with_thickness(2)
.render(target);
}

View File

@ -2,7 +2,7 @@ use crate::{
strutil::TString,
ui::{
component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe},
geometry::{Alignment, Grid, Rect},
geometry::{Alignment, Grid, Insets, Rect},
model_mercury::{
component::{Button, ButtonMsg, Swipe, SwipeDirection},
theme,
@ -12,6 +12,7 @@ use crate::{
};
pub const MNEMONIC_KEY_COUNT: usize = 9;
const BACK_BUTTON_RIGHT_EXPAND: i16 = 24;
pub enum MnemonicKeyboardMsg {
Confirmed,
@ -21,7 +22,9 @@ pub enum MnemonicKeyboardMsg {
pub struct MnemonicKeyboard<T> {
/// Initial prompt, displayed on empty input.
prompt: Child<Maybe<Label<'static>>>,
/// Backspace button.
/// Delete a char button.
erase: Child<Maybe<Button>>,
/// Go to previous word button
back: Child<Maybe<Button>>,
/// Input area, acting as the auto-complete and confirm button.
input: Child<Maybe<T>>,
@ -40,19 +43,24 @@ where
pub fn new(input: T, prompt: TString<'static>, can_go_back: bool) -> Self {
// Input might be already pre-filled
let prompt_visible = input.is_empty();
let erase_btn = Button::with_icon(theme::ICON_DELETE)
.styled(theme::button_default())
.with_expanded_touch_area(Insets::right(BACK_BUTTON_RIGHT_EXPAND))
.with_long_press(theme::ERASE_HOLD_DURATION);
let back_btn = Button::with_icon(theme::ICON_CHEVRON_LEFT)
.styled(theme::button_default())
.with_expanded_touch_area(Insets::right(BACK_BUTTON_RIGHT_EXPAND));
Self {
prompt: Child::new(Maybe::new(
theme::BG,
Label::centered(prompt, theme::TEXT_MAIN_GREY_LIGHT),
Label::centered(prompt, theme::TEXT_MAIN_GREY_LIGHT).vertically_centered(),
prompt_visible,
)),
erase: Child::new(Maybe::new(theme::BG, erase_btn, !prompt_visible)),
back: Child::new(Maybe::new(
theme::BG,
Button::with_icon(theme::ICON_DELETE)
.styled(theme::button_default())
.with_long_press(theme::ERASE_HOLD_DURATION),
!prompt_visible,
back_btn,
prompt_visible && can_go_back,
)),
input: Child::new(Maybe::new(theme::BG, input, !prompt_visible)),
keys: T::keys()
@ -86,15 +94,18 @@ where
}
/// After edit operations, we need to either show or hide the prompt, the
/// input, and the back button.
/// input, the erase button and the back button.
fn toggle_prompt_or_input(&mut self, ctx: &mut EventCtx) {
let prompt_visible = self.input.inner().inner().is_empty();
self.prompt
.mutate(ctx, |ctx, p| p.show_if(ctx, prompt_visible));
self.input
.mutate(ctx, |ctx, i| i.show_if(ctx, !prompt_visible));
self.back
self.erase
.mutate(ctx, |ctx, b| b.show_if(ctx, !prompt_visible));
self.back.mutate(ctx, |ctx, b| {
b.show_if(ctx, prompt_visible && self.can_go_back)
});
}
pub fn mnemonic(&self) -> Option<&'static str> {
@ -109,22 +120,25 @@ where
type Msg = MnemonicKeyboardMsg;
fn place(&mut self, bounds: Rect) -> Rect {
let height_input_area: i16 = 30;
let space_top: i16 = 8;
let height_input_area: i16 = 38;
let padding_top: i16 = 6;
let back_btn_area_width: i16 = 32;
let (remaining, keyboard_area) =
bounds.split_bottom(3 * theme::BUTTON_HEIGHT + 2 * theme::KEYBOARD_SPACING);
bounds.split_bottom(3 * theme::MNEMONIC_BUTTON_HEIGHT + 2 * theme::KEYBOARD_SPACING);
let prompt_area = remaining
.split_top(space_top)
.split_top(padding_top)
.1
.split_top(height_input_area)
.0;
assert!(prompt_area.height() == height_input_area);
let (back_btn_area, input_area) = prompt_area.split_left(30);
let (back_btn_area, input_area) = prompt_area.split_left(back_btn_area_width);
let input_area = input_area.inset(Insets::left(BACK_BUTTON_RIGHT_EXPAND));
let keyboard_grid = Grid::new(keyboard_area, 3, 3).with_spacing(theme::KEYBOARD_SPACING);
self.swipe.place(bounds);
self.prompt.place(prompt_area);
self.erase.place(back_btn_area);
self.back.place(back_btn_area);
self.input.place(input_area);
@ -135,8 +149,11 @@ where
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
// Swipe will cause going back to the previous word when allowed.
// Back button or swipe will cause going back to the previous word when allowed.
if self.can_go_back {
if let Some(ButtonMsg::Clicked) = self.back.event(ctx, event) {
return Some(MnemonicKeyboardMsg::Previous);
}
if let Some(SwipeDirection::Right) = self.swipe.event(ctx, event) {
return Some(MnemonicKeyboardMsg::Previous);
}
@ -155,7 +172,7 @@ where
_ => {}
}
match self.back.event(ctx, event) {
match self.erase.event(ctx, event) {
Some(ButtonMsg::Clicked) => {
self.input
.mutate(ctx, |ctx, i| i.inner_mut().on_backspace_click(ctx));
@ -182,7 +199,7 @@ where
}
fn paint(&mut self) {
paint_overlapping(&mut [&mut self.prompt, &mut self.input, &mut self.back]);
paint_overlapping(&mut [&mut self.prompt, &mut self.input, &mut self.erase]);
for btn in &mut self.keys {
btn.paint();
}
@ -191,9 +208,12 @@ where
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
if self.input.inner().inner().is_empty() {
self.prompt.render(target);
if self.can_go_back {
self.back.render(target);
}
} else {
self.input.render(target);
self.back.render(target);
self.erase.render(target);
}
for btn in &self.keys {
@ -205,6 +225,7 @@ where
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.prompt.bounds(sink);
self.input.bounds(sink);
self.erase.bounds(sink);
self.back.bounds(sink);
for btn in &self.keys {
btn.bounds(sink)

View File

@ -9,12 +9,12 @@ use crate::{
text::common::{TextBox, TextEdit},
Component, Event, EventCtx,
},
display,
geometry::{Alignment2D, Offset, Rect},
constant::WIDTH,
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
model_mercury::{
component::{
keyboard::{
common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard},
common::{render_pending_marker, render_pill_shape, MultiTapKeyboard},
mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT},
},
Button, ButtonContent, ButtonMsg,
@ -124,88 +124,35 @@ impl Component for Slip39Input {
}
fn paint(&mut self) {
let area = self.button.area();
let style = self.button.style();
// First, paint the button background.
self.button.paint_background(style);
// Content starts in the left-center point, offset by 16px to the right and 8px
// to the bottom.
let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8);
// To simplify things, we always copy the printed string here, even if it
// wouldn't be strictly necessary.
let mut text: String<MAX_LENGTH> = String::new();
if let Some(word) = self.final_word {
// We're done with input, paint the full word.
text.push_str(word)
.assert_if_debugging_ui("Text buffer is too small");
} else {
// Paint an asterisk for each letter of input.
for ch in iter::repeat('*').take(self.textbox.content().len()) {
text.push(ch)
.assert_if_debugging_ui("Text buffer is too small");
}
// If we're in the pending state, paint the pending character at the end.
if let (Some(key), Some(press)) =
(self.multi_tap.pending_key(), self.multi_tap.pending_press())
{
assert!(!Self::keys()[key].is_empty());
// Now we can be sure that the looped iterator will return a value.
let ch = unwrap!(Self::keys()[key].chars().cycle().nth(press));
text.pop();
text.push(ch)
.assert_if_debugging_ui("Text buffer is too small");
}
}
display::text_left(
text_baseline,
text.as_str(),
style.font,
style.text_color,
style.button_color,
);
// Paint the pending marker.
if self.multi_tap.pending_key().is_some() && self.final_word.is_none() {
paint_pending_marker(text_baseline, text.as_str(), style.font, style.text_color);
}
// Paint the icon.
if let ButtonContent::Icon(icon) = self.button.content() {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge.
let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0);
icon.draw(
icon_center,
Alignment2D::CENTER,
style.icon_color,
style.button_color,
);
}
todo!("remove when ui-t3t1 done")
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let area = self.button.area();
let style = self.button.style();
// First, paint the button background.
self.button.render_background(target, style);
// Content starts in the left-center point, offset by 16px to the right and 8px
// to the bottom.
let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8);
// Content is center-aligned
let text_base_y = area.left_center().y + style.font.allcase_text_height() / 2;
let text_center = Point::new(WIDTH / 2, text_base_y);
// To simplify things, we always copy the printed string here, even if it
// wouldn't be strictly necessary.
let mut text: String<MAX_LENGTH> = String::new();
if let Some(word) = self.final_word {
// We're done with input, paint the full word.
text.push_str(word)
.assert_if_debugging_ui("Text buffer is too small");
let pill_base = Point::new(
style.font.horz_center(0, WIDTH, text.as_str()),
text_center.y,
);
render_pill_shape(target, pill_base, text.as_str(), style, Some(area));
// Icon is painted in the right-center point, 10px from the right edge.
let icon_right_center = area.right_center() - Offset::x(10);
shape::ToifImage::new(icon_right_center, theme::ICON_SIMPLE_CHECKMARK24.toif)
.with_align(Alignment2D::CENTER_RIGHT)
.with_fg(style.icon_color)
.render(target);
} else {
// Paint an asterisk for each letter of input.
for ch in iter::repeat('*').take(self.textbox.content().len()) {
@ -224,32 +171,26 @@ impl Component for Slip39Input {
.assert_if_debugging_ui("Text buffer is too small");
}
}
shape::Text::new(text_baseline, text.as_str())
shape::Text::new(text_center, text.as_str())
.with_font(style.font)
.with_fg(style.text_color)
.with_align(Alignment::Center)
.render(target);
// Paint the pending marker.
let text_base = Point::new(
style.font.horz_center(0, WIDTH, text.as_str()),
text_center.y,
);
if self.multi_tap.pending_key().is_some() && self.final_word.is_none() {
render_pending_marker(
target,
text_baseline,
text_base,
text.as_str(),
style.font,
style.text_color,
);
}
// Paint the icon.
if let ButtonContent::Icon(icon) = self.button.content() {
// Icon is painted in the right-center point, of expected size 16x16 pixels, and
// 16px from the right edge.
let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0);
shape::ToifImage::new(icon_center, icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(style.icon_color)
.render(target);
}
}
#[cfg(feature = "ui_bounds")]
@ -262,7 +203,7 @@ impl Slip39Input {
pub fn new() -> Self {
Self {
// Button has the same style the whole time
button: Button::empty().styled(theme::button_pin_confirm()),
button: Button::empty().styled(theme::button_recovery_confirm()),
textbox: TextBox::empty(),
multi_tap: MultiTapKeyboard::new(),
final_word: None,
@ -280,7 +221,7 @@ impl Slip39Input {
Self {
// Button has the same style the whole time
button: Button::empty().styled(theme::button_pin_confirm()),
button: Button::empty().styled(theme::button_recovery_confirm()),
textbox: TextBox::new(buff),
multi_tap: MultiTapKeyboard::new(),
final_word,
@ -356,7 +297,7 @@ impl Slip39Input {
// Confirm button.
self.button.enable(ctx);
self.button
.set_content(ctx, ButtonContent::Icon(theme::ICON_CONFIRM_INPUT));
.set_content(ctx, ButtonContent::Icon(theme::ICON_SIMPLE_CHECKMARK24));
} else {
// Disabled button.
self.button.disable(ctx);

View File

@ -75,6 +75,7 @@ include_icon!(ICON_PAGE_UP, "model_mercury/res/page_up20.toif");
// 24x24
include_icon!(ICON_CANCEL, "model_mercury/res/cancel24.toif");
include_icon!(ICON_CHEVRON_LEFT, "model_mercury/res/chevron_left24.toif");
include_icon!(ICON_CHEVRON_RIGHT, "model_mercury/res/chevron_right24.toif");
include_icon!(ICON_DOWNLOAD, "model_mercury/res/download24.toif");
include_icon!(ICON_KEY, "model_mercury/res/key20.toif");
@ -576,27 +577,28 @@ pub const fn button_passphrase_next() -> ButtonStyleSheet {
}
}
pub const fn button_bip39_autocomplete() -> ButtonStyleSheet {
pub const fn button_recovery_confirm() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: BG,
icon_color: GREY_LIGHT,
background_color: BG,
font: Font::DEMIBOLD,
text_color: GREEN_LIME,
button_color: GREEN_LIGHT,
icon_color: GREEN_LIME,
background_color: GREEN_DARK,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: BG,
icon_color: GREY_LIGHT,
background_color: BG,
font: Font::DEMIBOLD,
text_color: GREEN_DARK,
button_color: GREEN_LIGHT,
icon_color: GREEN_DARK,
background_color: GREEN_LIGHT,
},
// used in SLIP-39 recovery for "*"
disabled: &ButtonStyle {
font: Font::MONO,
text_color: FG,
font: Font::DEMIBOLD,
text_color: GREY_LIGHT,
button_color: BG,
icon_color: GREY_LIGHT,
icon_color: BG,
background_color: BG,
},
}
@ -605,24 +607,52 @@ pub const fn button_bip39_autocomplete() -> ButtonStyleSheet {
pub const fn button_suggestion_confirm() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
font: Font::DEMIBOLD,
text_color: GREY_LIGHT, // difference
button_color: GREEN_LIGHT,
icon_color: GREEN_LIME,
background_color: GREEN_DARK,
},
active: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: GREEN_LIME,
button_color: GREEN_LIGHT,
icon_color: GREEN_DARK,
background_color: GREEN_LIGHT,
},
// not used
disabled: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: BG,
button_color: BG,
icon_color: BG,
background_color: BG,
},
}
}
pub const fn button_recovery_autocomplete() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::DEMIBOLD,
text_color: GREY_LIGHT,
button_color: GREEN,
button_color: GREY_EXTRA_DARK,
icon_color: GREY_LIGHT,
background_color: BG,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREEN_DARK,
icon_color: GREY_LIGHT,
background_color: BG,
font: Font::DEMIBOLD,
text_color: BG,
button_color: FG,
icon_color: BG,
background_color: FG,
},
// not used
disabled: &ButtonStyle {
font: Font::MONO,
text_color: GREY_LIGHT,
button_color: GREY_DARK,
icon_color: GREY_LIGHT,
font: Font::DEMIBOLD,
text_color: BG,
button_color: BG,
icon_color: BG,
background_color: BG,
},
}
@ -631,24 +661,25 @@ pub const fn button_suggestion_confirm() -> ButtonStyleSheet {
pub const fn button_suggestion_autocomplete() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: GREY_LIGHT,
button_color: GREY_DARK, // same as PIN buttons
icon_color: GREY_LIGHT,
font: Font::DEMIBOLD,
text_color: GREY,
button_color: BG,
icon_color: BG,
background_color: BG,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREEN_DARK,
icon_color: GREY_LIGHT,
background_color: BG,
text_color: BG,
button_color: FG,
icon_color: BG,
background_color: FG,
},
// not used
disabled: &ButtonStyle {
font: Font::MONO,
text_color: GREY_LIGHT,
text_color: BG,
button_color: BG,
icon_color: GREY_LIGHT,
icon_color: BG,
background_color: BG,
},
}

View File

@ -34,7 +34,7 @@ async def request_word_count(dry_run: bool) -> int:
async def request_word(
word_index: int, word_count: int, is_slip39: bool, prefill_word: str = ""
) -> str:
prompt = TR.recovery__type_word_x_of_y_template.format(word_index + 1, word_count)
prompt = TR.recovery__word_x_of_y_template.format(word_index + 1, word_count)
can_go_back = word_index > 0
if is_slip39:
keyboard = RustLayout(