1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-15 11:08:08 +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::{
base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx,
},
display::{self, toif::Icon, Color},
display::{self, Color, Font},
geometry::{Alignment, Insets, Offset, Rect},
util::icon_text_center,
};
pub struct Frame<T, U> {
@ -116,7 +115,6 @@ where
pub struct NotificationFrame<T, U> {
area: Rect,
icon: Icon,
title: U,
content: Child<T>,
}
@ -128,13 +126,10 @@ where
{
const HEIGHT: i16 = 36;
const COLOR: Color = theme::YELLOW;
const TEXT_OFFSET: Offset = Offset::new(1, -2);
const ICON_SPACE: i16 = 8;
const BORDER: i16 = 6;
pub fn new(icon: Icon, title: U, content: T) -> Self {
pub fn new(title: U, content: T) -> Self {
Self {
icon,
title,
area: Rect::zero(),
content: Child::new(content),
@ -145,22 +140,18 @@ where
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
.inset(Insets::uniform(Self::BORDER))
.split_top(Self::HEIGHT);
let style = TextStyle {
background_color: color,
..theme::TEXT_BOLD
};
let font = Font::BOLD;
display::rect_fill_rounded(area, color, theme::BG, 2);
icon_text_center(
area.center(),
icon,
Self::ICON_SPACE,
display::text_center(
area.center() + Offset::y((font.text_max_height() - font.text_baseline()) / 2),
title,
style,
Self::TEXT_OFFSET,
Font::BOLD,
theme::FG,
color,
);
}
}
@ -184,7 +175,7 @@ where
}
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();
}

View File

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

View File

@ -43,7 +43,7 @@ where
Icon::new(theme::ICON_BACK),
Offset::new(30, 17),
)
.styled(theme::button_clear())
.styled(theme::button_reset())
.with_long_press(theme::ERASE_HOLD_DURATION),
)),
input: Child::new(Maybe::hidden(theme::BG, input)),
@ -96,8 +96,10 @@ where
type Msg = MnemonicKeyboardMsg;
fn place(&mut self, bounds: Rect) -> Rect {
let grid =
Grid::new(bounds.inset(theme::borders()), 4, 3).with_spacing(theme::KEYBOARD_SPACING);
let (_, bounds) = bounds
.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 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},
display,
display::toif::Icon,
geometry::{Grid, Insets, Offset, Rect},
geometry::{Grid, Offset, Rect},
model_tt::component::{
button::{Button, ButtonContent, ButtonMsg},
keyboard::common::{
paint_pending_marker, MultiTapKeyboard, TextBox, HEADER_HEIGHT, HEADER_PADDING_BOTTOM,
HEADER_PADDING_SIDE,
},
keyboard::common::{paint_pending_marker, MultiTapKeyboard, TextBox},
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
@ -41,6 +38,7 @@ const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
];
const MAX_LENGTH: usize = 50;
const INPUT_AREA_HEIGHT: i16 = ScrollBar::DOT_SIZE + 9;
impl PassphraseKeyboard {
pub fn new() -> Self {
@ -59,8 +57,9 @@ impl PassphraseKeyboard {
.initially_enabled(false)
.with_long_press(theme::ERASE_HOLD_DURATION)
.into_child(),
keys: KEYBOARD[STARTING_PAGE]
.map(|text| Child::new(Button::new(Self::key_content(text)))),
keys: KEYBOARD[STARTING_PAGE].map(|text| {
Child::new(Button::new(Self::key_content(text)).styled(theme::button_pin()))
}),
scrollbar: ScrollBar::horizontal(),
fade: false,
}
@ -152,14 +151,13 @@ impl Component for PassphraseKeyboard {
fn place(&mut self, bounds: Rect) -> Rect {
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) =
input_area.split_bottom(ScrollBar::DOT_SIZE + theme::KEYBOARD_SPACING);
let (input_area, scroll_area) = input_area.split_bottom(INPUT_AREA_HEIGHT);
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 back_btn_area = key_grid.cell(9);

View File

@ -59,8 +59,8 @@ where
T: AsRef<str>,
{
// Label position fine-tuning.
const MAJOR_OFF: Offset = Offset::y(-2);
const MINOR_OFF: Offset = Offset::y(-1);
const MAJOR_OFF: Offset = Offset::y(11);
const MINOR_OFF: Offset = Offset::y(11);
pub fn new(
major_prompt: T,
@ -165,14 +165,14 @@ where
// Prompts and PIN dots display.
let (header, keypad) = bounds
.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);
// 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 minor_area = prompt.translate(Self::MINOR_OFF);
// 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.
self.textbox_pad.place(header);

View File

@ -224,13 +224,13 @@ impl Slip39Input {
if self.final_word.is_some() {
// Confirm button.
self.button.enable(ctx);
self.button.set_stylesheet(ctx, theme::button_confirm());
self.button.set_stylesheet(ctx, theme::button_pin_confirm());
self.button
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_CONFIRM)));
.set_content(ctx, ButtonContent::Icon(Icon::new(theme::ICON_LIST_CHECK)));
} else {
// Disabled button.
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(""));
}
}

View File

@ -22,7 +22,7 @@ pub enum SelectWordCountMsg {
impl SelectWordCount {
pub fn new() -> Self {
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;
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);
for (btn, (x, y)) in self.button.iter_mut().zip(CELLS) {
btn.place(grid.cells(GridCellSpan {

View File

@ -87,13 +87,12 @@ where
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.paragraphs.place(content_area);
self.paragraphs_pad.place(content_area);
self.info_button.place(grid.row_col(0, 0));
self.confirm_button
.place(grid.row_col(0, 1).union(grid.row_col(0, 2)));
self.confirm_button.place(grid.row_col(0, 1));
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 button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let paragraphs = Paragraphs::new(Paragraph::new(
&theme::TEXT_NORMAL,
StrBuffer::from("By continuing you agree\nto Trezor Company's\nterms and conditions."),
));
let url = FormattedText::new(
theme::TEXT_NORMAL,
theme::FORMATTED,
"More info at {demibold}trezor.io/tos",
);
let paragraphs = Paragraphs::new([
Paragraph::new(
&theme::TEXT_NORMAL,
StrBuffer::from(
"By continuing you agree\nto Trezor Company's\nterms and conditions.\r",
),
),
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(
Button::with_icon(Icon::new(theme::ICON_CANCEL)),
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(
theme::label_title(),
title,
Dialog::new(
(
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,
),
Dialog::new(paragraphs, buttons),
)
.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 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(),
])
.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 {
LayoutObj::new(NotificationFrame::new(
Icon::new(theme::ICON_WARN),
notification,
Dialog::new(
paragraphs,
@ -1321,7 +1313,6 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
))?
} else {
LayoutObj::new(NotificationFrame::new(
Icon::new(theme::ICON_WARN),
notification,
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 {
"SEED CHECK"
} else {
"RECOVERY MODE"
"WALLET RECOVERY"
};
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(

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 {
ButtonStyleSheet {
normal: &ButtonStyle {

View File

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

View File

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

View File

@ -53,7 +53,7 @@ def test_abort(emulator: Emulator):
assert layout.get_title() == "WALLET RECOVERY"
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)
debug = device_handler.debuglink()
@ -63,7 +63,7 @@ def test_abort(emulator: Emulator):
# no waiting for layout because layout doesn't change
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)
assert layout.get_title() == "ABORT RECOVERY"