1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-15 19:18:11 +00:00

fix(core/ui): style update: mnemonic keyboards

[no changelog]
This commit is contained in:
Martin Milata 2023-03-10 16:39:29 +01:00
parent f91ea05449
commit e16a37b0cf
13 changed files with 127 additions and 77 deletions

View File

@ -3,9 +3,8 @@ use crate::ui::{
component::{ component::{
base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx, base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx,
}, },
display::{self, toif::Icon, Color}, display::{self, Color, Font},
geometry::{Alignment, Insets, Offset, Rect}, geometry::{Alignment, Insets, Offset, Rect},
util::icon_text_center,
}; };
pub struct Frame<T, U> { pub struct Frame<T, U> {
@ -116,7 +115,6 @@ where
pub struct NotificationFrame<T, U> { pub struct NotificationFrame<T, U> {
area: Rect, area: Rect,
icon: Icon,
title: U, title: U,
content: Child<T>, content: Child<T>,
} }
@ -128,13 +126,10 @@ where
{ {
const HEIGHT: i16 = 36; const HEIGHT: i16 = 36;
const COLOR: Color = theme::YELLOW; const COLOR: Color = theme::YELLOW;
const TEXT_OFFSET: Offset = Offset::new(1, -2);
const ICON_SPACE: i16 = 8;
const BORDER: i16 = 6; const BORDER: i16 = 6;
pub fn new(icon: Icon, title: U, content: T) -> Self { pub fn new(title: U, content: T) -> Self {
Self { Self {
icon,
title, title,
area: Rect::zero(), area: Rect::zero(),
content: Child::new(content), content: Child::new(content),
@ -145,22 +140,18 @@ where
self.content.inner() self.content.inner()
} }
pub fn paint_notification(area: Rect, icon: Icon, title: &str, color: Color) { pub fn paint_notification(area: Rect, title: &str, color: Color) {
let (area, _) = area let (area, _) = area
.inset(Insets::uniform(Self::BORDER)) .inset(Insets::uniform(Self::BORDER))
.split_top(Self::HEIGHT); .split_top(Self::HEIGHT);
let style = TextStyle { let font = Font::BOLD;
background_color: color,
..theme::TEXT_BOLD
};
display::rect_fill_rounded(area, color, theme::BG, 2); display::rect_fill_rounded(area, color, theme::BG, 2);
icon_text_center( display::text_center(
area.center(), area.center() + Offset::y((font.text_max_height() - font.text_baseline()) / 2),
icon,
Self::ICON_SPACE,
title, title,
style, Font::BOLD,
Self::TEXT_OFFSET, theme::FG,
color,
); );
} }
} }
@ -184,7 +175,7 @@ where
} }
fn paint(&mut self) { fn paint(&mut self) {
Self::paint_notification(self.area, self.icon, self.title.as_ref(), Self::COLOR); Self::paint_notification(self.area, self.title.as_ref(), Self::COLOR);
self.content.paint(); self.content.paint();
} }

View File

@ -215,20 +215,21 @@ impl Bip39Input {
{ {
// Confirm button. // Confirm button.
self.button.enable(ctx); self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_confirm()); self.button.set_stylesheet(ctx, theme::button_pin_confirm());
self.button self.button
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM))); .set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_LIST_CHECK)));
} else { } else {
// Auto-complete button. // Auto-complete button.
self.button.enable(ctx); self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_default()); self.button
.set_stylesheet(ctx, theme::button_pin_autocomplete());
self.button self.button
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CLICK))); .set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CLICK)));
} }
} else { } else {
// Disabled button. // Disabled button.
self.button.disable(ctx); self.button.disable(ctx);
self.button.set_stylesheet(ctx, theme::button_default()); self.button.set_stylesheet(ctx, theme::button_pin());
self.button.set_content(ctx, ButtonContent::Text("")); self.button.set_content(ctx, ButtonContent::Text(""));
} }
} }

View File

@ -43,7 +43,7 @@ where
Icon::new(theme::ICON_BACK), Icon::new(theme::ICON_BACK),
Offset::new(30, 17), Offset::new(30, 17),
) )
.styled(theme::button_clear()) .styled(theme::button_reset())
.with_long_press(theme::ERASE_HOLD_DURATION), .with_long_press(theme::ERASE_HOLD_DURATION),
)), )),
input: Child::new(Maybe::hidden(theme::BG, input)), input: Child::new(Maybe::hidden(theme::BG, input)),
@ -96,8 +96,10 @@ where
type Msg = MnemonicKeyboardMsg; type Msg = MnemonicKeyboardMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let grid = let (_, bounds) = bounds
Grid::new(bounds.inset(theme::borders()), 4, 3).with_spacing(theme::KEYBOARD_SPACING); .inset(theme::borders())
.split_bottom(4 * theme::MNEMONIC_BUTTON_HEIGHT + 3 * theme::KEYBOARD_SPACING);
let grid = Grid::new(bounds, 4, 3).with_spacing(theme::KEYBOARD_SPACING);
let back_area = grid.row_col(0, 0); let back_area = grid.row_col(0, 0);
let input_area = grid.row_col(0, 1).union(grid.row_col(0, 3)); let input_area = grid.row_col(0, 1).union(grid.row_col(0, 3));

View File

@ -2,13 +2,10 @@ use crate::ui::{
component::{base::ComponentExt, Child, Component, Event, EventCtx, Never}, component::{base::ComponentExt, Child, Component, Event, EventCtx, Never},
display, display,
display::toif::Icon, display::toif::Icon,
geometry::{Grid, Insets, Offset, Rect}, geometry::{Grid, Offset, Rect},
model_tt::component::{ model_tt::component::{
button::{Button, ButtonContent, ButtonMsg}, button::{Button, ButtonContent, ButtonMsg},
keyboard::common::{ keyboard::common::{paint_pending_marker, MultiTapKeyboard, TextBox},
paint_pending_marker, MultiTapKeyboard, TextBox, HEADER_HEIGHT, HEADER_PADDING_BOTTOM,
HEADER_PADDING_SIDE,
},
swipe::{Swipe, SwipeDirection}, swipe::{Swipe, SwipeDirection},
theme, ScrollBar, theme, ScrollBar,
}, },
@ -41,6 +38,7 @@ const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
]; ];
const MAX_LENGTH: usize = 50; const MAX_LENGTH: usize = 50;
const INPUT_AREA_HEIGHT: i16 = ScrollBar::DOT_SIZE + 9;
impl PassphraseKeyboard { impl PassphraseKeyboard {
pub fn new() -> Self { pub fn new() -> Self {
@ -59,8 +57,9 @@ impl PassphraseKeyboard {
.initially_enabled(false) .initially_enabled(false)
.with_long_press(theme::ERASE_HOLD_DURATION) .with_long_press(theme::ERASE_HOLD_DURATION)
.into_child(), .into_child(),
keys: KEYBOARD[STARTING_PAGE] keys: KEYBOARD[STARTING_PAGE].map(|text| {
.map(|text| Child::new(Button::new(Self::key_content(text)))), Child::new(Button::new(Self::key_content(text)).styled(theme::button_pin()))
}),
scrollbar: ScrollBar::horizontal(), scrollbar: ScrollBar::horizontal(),
fade: false, fade: false,
} }
@ -152,14 +151,13 @@ impl Component for PassphraseKeyboard {
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let bounds = bounds.inset(theme::borders()); let bounds = bounds.inset(theme::borders());
let (input_area, key_grid_area) = bounds.split_top(HEADER_HEIGHT + HEADER_PADDING_BOTTOM); let (input_area, key_grid_area) =
bounds.split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING);
let (input_area, scroll_area) = let (input_area, scroll_area) = input_area.split_bottom(INPUT_AREA_HEIGHT);
input_area.split_bottom(ScrollBar::DOT_SIZE + theme::KEYBOARD_SPACING);
let (scroll_area, _) = scroll_area.split_top(ScrollBar::DOT_SIZE); let (scroll_area, _) = scroll_area.split_top(ScrollBar::DOT_SIZE);
let input_area = input_area.inset(Insets::sides(HEADER_PADDING_SIDE));
let key_grid = Grid::new(key_grid_area, 4, 3).with_spacing(theme::KEYBOARD_SPACING); let key_grid = Grid::new(key_grid_area, 4, 3).with_spacing(theme::BUTTON_SPACING);
let confirm_btn_area = key_grid.cell(11); let confirm_btn_area = key_grid.cell(11);
let back_btn_area = key_grid.cell(9); let back_btn_area = key_grid.cell(9);

View File

@ -59,8 +59,8 @@ where
T: AsRef<str>, T: AsRef<str>,
{ {
// Label position fine-tuning. // Label position fine-tuning.
const MAJOR_OFF: Offset = Offset::y(-2); const MAJOR_OFF: Offset = Offset::y(11);
const MINOR_OFF: Offset = Offset::y(-1); const MINOR_OFF: Offset = Offset::y(11);
pub fn new( pub fn new(
major_prompt: T, major_prompt: T,
@ -165,14 +165,14 @@ where
// Prompts and PIN dots display. // Prompts and PIN dots display.
let (header, keypad) = bounds let (header, keypad) = bounds
.inset(borders_no_top) .inset(borders_no_top)
.split_top(theme::borders().top + HEADER_HEIGHT + HEADER_PADDING_BOTTOM); .split_bottom(4 * theme::PIN_BUTTON_HEIGHT + 3 * theme::BUTTON_SPACING);
let prompt = header.inset(HEADER_PADDING); let prompt = header.inset(HEADER_PADDING);
// the inset -3 is a workaround for long text in "re-enter wipe code" // the inset -3 is a workaround for long text in "re-enter wipe code"
let major_area = prompt.translate(Self::MAJOR_OFF).inset(Insets::right(-3)); let major_area = prompt.translate(Self::MAJOR_OFF).inset(Insets::right(-3));
let minor_area = prompt.translate(Self::MINOR_OFF); let minor_area = prompt.translate(Self::MINOR_OFF);
// Control buttons. // Control buttons.
let grid = Grid::new(keypad, 4, 3).with_spacing(theme::KEYBOARD_SPACING); let grid = Grid::new(keypad, 4, 3).with_spacing(theme::BUTTON_SPACING);
// Prompts and PIN dots display. // Prompts and PIN dots display.
self.textbox_pad.place(header); self.textbox_pad.place(header);

View File

@ -224,13 +224,13 @@ impl Slip39Input {
if self.final_word.is_some() { if self.final_word.is_some() {
// Confirm button. // Confirm button.
self.button.enable(ctx); self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_confirm()); self.button.set_stylesheet(ctx, theme::button_pin_confirm());
self.button self.button
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM))); .set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_LIST_CHECK)));
} else { } else {
// Disabled button. // Disabled button.
self.button.disable(ctx); self.button.disable(ctx);
self.button.set_stylesheet(ctx, theme::button_default()); self.button.set_stylesheet(ctx, theme::button_pin());
self.button.set_content(ctx, ButtonContent::Text("")); self.button.set_content(ctx, ButtonContent::Text(""));
} }
} }

View File

@ -22,7 +22,7 @@ pub enum SelectWordCountMsg {
impl SelectWordCount { impl SelectWordCount {
pub fn new() -> Self { pub fn new() -> Self {
SelectWordCount { SelectWordCount {
button: LABELS.map(Button::with_text), button: LABELS.map(|t| Button::with_text(t).styled(theme::button_pin())),
} }
} }
} }
@ -31,7 +31,7 @@ impl Component for SelectWordCount {
type Msg = SelectWordCountMsg; type Msg = SelectWordCountMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let (_, bounds) = bounds.split_bottom(theme::button_rows(2)); let (_, bounds) = bounds.split_bottom(2 * theme::BUTTON_HEIGHT + theme::BUTTON_SPACING);
let grid = Grid::new(bounds, 2, 6).with_spacing(theme::BUTTON_SPACING); let grid = Grid::new(bounds, 2, 6).with_spacing(theme::BUTTON_SPACING);
for (btn, (x, y)) in self.button.iter_mut().zip(CELLS) { for (btn, (x, y)) in self.button.iter_mut().zip(CELLS) {
btn.place(grid.cells(GridCellSpan { btn.place(grid.cells(GridCellSpan {

View File

@ -87,13 +87,12 @@ where
theme::CONTENT_BORDER, theme::CONTENT_BORDER,
)); ));
let grid = Grid::new(button_area, 1, 3).with_spacing(theme::KEYBOARD_SPACING); let grid = Grid::new(button_area, 1, 2).with_spacing(theme::KEYBOARD_SPACING);
self.input.place(input_area); self.input.place(input_area);
self.paragraphs.place(content_area); self.paragraphs.place(content_area);
self.paragraphs_pad.place(content_area); self.paragraphs_pad.place(content_area);
self.info_button.place(grid.row_col(0, 0)); self.info_button.place(grid.row_col(0, 0));
self.confirm_button self.confirm_button.place(grid.row_col(0, 1));
.place(grid.row_col(0, 1).union(grid.row_col(0, 2)));
bounds bounds
} }

View File

@ -588,15 +588,16 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let paragraphs = Paragraphs::new(Paragraph::new( let paragraphs = Paragraphs::new([
&theme::TEXT_NORMAL, Paragraph::new(
StrBuffer::from("By continuing you agree\nto Trezor Company's\nterms and conditions."), &theme::TEXT_NORMAL,
)); StrBuffer::from(
let url = FormattedText::new( "By continuing you agree\nto Trezor Company's\nterms and conditions.\r",
theme::TEXT_NORMAL, ),
theme::FORMATTED, ),
"More info at {demibold}trezor.io/tos", Paragraph::new(&theme::TEXT_NORMAL, StrBuffer::from("More info at")),
); Paragraph::new(&theme::TEXT_DEMIBOLD, StrBuffer::from("trezor.io/tos")),
]);
let buttons = Button::cancel_confirm( let buttons = Button::cancel_confirm(
Button::with_icon(Icon::new(theme::ICON_CANCEL)), Button::with_icon(Icon::new(theme::ICON_CANCEL)),
Button::with_text(button).styled(theme::button_confirm()), Button::with_text(button).styled(theme::button_confirm()),
@ -606,15 +607,7 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
Frame::left_aligned( Frame::left_aligned(
theme::label_title(), theme::label_title(),
title, title,
Dialog::new( Dialog::new(paragraphs, buttons),
(
GridPlaced::new(paragraphs)
.with_grid(3, 1)
.with_from_to((0, 0), (1, 0)),
GridPlaced::new(url).with_grid(3, 1).with_row_col(2, 0),
),
buttons,
),
) )
.with_border(theme::borders()), .with_border(theme::borders()),
)?; )?;
@ -1299,7 +1292,7 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false).unwrap(); let info_button: bool = kwargs.get_or(Qstr::MP_QSTR_info_button, false).unwrap();
let paragraphs = Paragraphs::new([ let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_BOLD, title).centered(), Paragraph::new(&theme::TEXT_DEMIBOLD, title).centered(),
Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, description).centered(), Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, description).centered(),
]) ])
.with_spacing(theme::RECOVERY_SPACING); .with_spacing(theme::RECOVERY_SPACING);
@ -1312,7 +1305,6 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
let obj = if info_button { let obj = if info_button {
LayoutObj::new(NotificationFrame::new( LayoutObj::new(NotificationFrame::new(
Icon::new(theme::ICON_WARN),
notification, notification,
Dialog::new( Dialog::new(
paragraphs, paragraphs,
@ -1321,7 +1313,6 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
))? ))?
} else { } else {
LayoutObj::new(NotificationFrame::new( LayoutObj::new(NotificationFrame::new(
Icon::new(theme::ICON_WARN),
notification, notification,
Dialog::new(paragraphs, Button::cancel_confirm_text(None, Some(button))), Dialog::new(paragraphs, Button::cancel_confirm_text(None, Some(button))),
))? ))?
@ -1341,11 +1332,15 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu
let title = if dry_run { let title = if dry_run {
"SEED CHECK" "SEED CHECK"
} else { } else {
"RECOVERY MODE" "WALLET RECOVERY"
}; };
let paragraphs = Paragraphs::new( let paragraphs = Paragraphs::new(
Paragraph::new(&theme::TEXT_BOLD, StrBuffer::from("Number of words?")).centered(), Paragraph::new(
&theme::TEXT_DEMIBOLD,
StrBuffer::from("Select number of words in your recovery seed."),
)
.centered(),
); );
let obj = LayoutObj::new( let obj = LayoutObj::new(

View File

@ -384,6 +384,70 @@ pub const fn button_pin() -> ButtonStyleSheet {
} }
} }
pub const fn button_pin_confirm() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREEN,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREY_DARK,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: Font::MONO,
text_color: GREY_LIGHT,
button_color: GREEN,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
}
}
pub const fn button_pin_autocomplete() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: BG,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: Font::MONO,
text_color: FG,
button_color: GREY_DARK,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: Font::MONO,
text_color: GREY_LIGHT,
button_color: BG,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
}
}
pub const fn button_counter() -> ButtonStyleSheet { pub const fn button_counter() -> ButtonStyleSheet {
ButtonStyleSheet { ButtonStyleSheet {
normal: &ButtonStyle { normal: &ButtonStyle {

View File

@ -42,7 +42,7 @@ async def request_word_count(ctx: GenericContext, dry_run: bool) -> int:
async def request_word( async def request_word(
ctx: GenericContext, word_index: int, word_count: int, is_slip39: bool ctx: GenericContext, word_index: int, word_count: int, is_slip39: bool
) -> str: ) -> str:
prompt = f"Type word {word_index + 1} of {word_count}:" prompt = f"Type word {word_index + 1} of {word_count}"
if is_slip39: if is_slip39:
keyboard = RustLayout(trezorui2.request_slip39(prompt=prompt)) keyboard = RustLayout(trezorui2.request_slip39(prompt=prompt))
else: else:

View File

@ -41,7 +41,7 @@ def select_number_of_words(
assert layout.text == "WordSelector" assert layout.text == "WordSelector"
else: else:
# Two title options # Two title options
assert layout.get_title() in ("SEED CHECK", "RECOVERY MODE") assert layout.get_title() in ("SEED CHECK", "WALLET RECOVERY")
# click the number # click the number
word_option_offset = 6 word_option_offset = 6

View File

@ -53,7 +53,7 @@ def test_abort(emulator: Emulator):
assert layout.get_title() == "WALLET RECOVERY" assert layout.get_title() == "WALLET RECOVERY"
layout = debug.click(buttons.OK, wait=True) layout = debug.click(buttons.OK, wait=True)
assert "Select number of words" in layout.text assert "Select number of words" in layout.get_content()
device_handler.restart(emulator) device_handler.restart(emulator)
debug = device_handler.debuglink() debug = device_handler.debuglink()
@ -63,7 +63,7 @@ def test_abort(emulator: Emulator):
# no waiting for layout because layout doesn't change # no waiting for layout because layout doesn't change
layout = debug.read_layout() layout = debug.read_layout()
assert "Select number of words" in layout.text assert "Select number of words" in layout.get_content()
layout = debug.click(buttons.CANCEL, wait=True) layout = debug.click(buttons.CANCEL, wait=True)
assert layout.get_title() == "ABORT RECOVERY" assert layout.get_title() == "ABORT RECOVERY"