1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-01 19:38:33 +00:00

WIP - bip39 design improvements

This commit is contained in:
grdddj 2022-11-14 09:49:57 +01:00
parent f1536c602b
commit 3434838bbe
8 changed files with 89 additions and 42 deletions

View File

@ -515,7 +515,13 @@ impl<T: Clone + AsRef<str>> ButtonDetails<T> {
self self
} }
/// Duration of the hold-to-confirm. /// Default duration of the hold-to-confirm.
pub fn with_default_duration(mut self) -> Self {
self.duration = Some(Duration::from_millis(1000));
self
}
/// Specific duration of the hold-to-confirm.
pub fn with_duration(mut self, duration: Duration) -> Self { pub fn with_duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration); self.duration = Some(duration);
self self

View File

@ -83,6 +83,21 @@ pub fn paint_header_left<T: AsRef<str>>(title: T, area: Rect) -> i16 {
text_heigth text_heigth
} }
/// Display title/header centered at the top of the given area.
/// Returning the painted height of the whole header.
pub fn paint_header_centered<T: AsRef<str>>(title: T, area: Rect) -> i16 {
let text_heigth = theme::FONT_HEADER.text_height();
let title_baseline = area.top_center() + Offset::y(text_heigth);
display::text_center(
title_baseline,
title.as_ref(),
theme::FONT_HEADER,
theme::FG,
theme::BG,
);
text_heigth
}
/// Draws icon and text on the same line - icon on the left. /// Draws icon and text on the same line - icon on the left.
pub fn icon_with_text<T: AsRef<str>>(baseline: Point, icon: Icon, text: T, font: Font) { pub fn icon_with_text<T: AsRef<str>>(baseline: Point, icon: Icon, text: T, font: Font) {
icon.draw_bottom_left(baseline, theme::FG, theme::BG); icon.draw_bottom_left(baseline, theme::FG, theme::BG);

View File

@ -5,9 +5,12 @@ use crate::ui::{
}; };
/// Component for holding another component and displaying a title. /// Component for holding another component and displaying a title.
/// Also is allocating space for a scrollbar.
pub struct Frame<T, U> { pub struct Frame<T, U> {
area: Rect, area: Rect,
title: U, title: U,
title_centered: bool,
account_for_scrollbar: bool,
content: Child<T>, content: Child<T>,
} }
@ -20,6 +23,8 @@ where
Self { Self {
title, title,
area: Rect::zero(), area: Rect::zero(),
title_centered: false,
account_for_scrollbar: true,
content: Child::new(content), content: Child::new(content),
} }
} }
@ -27,6 +32,20 @@ where
pub fn inner(&self) -> &T { pub fn inner(&self) -> &T {
self.content.inner() self.content.inner()
} }
/// Aligning the title to the center, instead of the left.
/// Also disabling scrollbar in this case, as they are not compatible.
pub fn with_title_center(mut self, title_centered: bool) -> Self {
self.title_centered = title_centered;
self.account_for_scrollbar = false;
self
}
/// Allocating space for scrollbar in the top right. True by default.
pub fn with_scrollbar(mut self, account_for_scrollbar: bool) -> Self {
self.account_for_scrollbar = account_for_scrollbar;
self
}
} }
impl<T, U> Component for Frame<T, U> impl<T, U> Component for Frame<T, U>
@ -43,10 +62,16 @@ where
bounds.split_top(theme::FONT_HEADER.line_height()); bounds.split_top(theme::FONT_HEADER.line_height());
let content_area = content_area.inset(Insets::top(TITLE_SPACE)); let content_area = content_area.inset(Insets::top(TITLE_SPACE));
let (title_area, scrollbar_area) = // Title area is different based on scrollbar.
title_and_scrollbar_area.split_right(ScrollBar::MAX_WIDTH); let title_area = if self.account_for_scrollbar {
let (title_area, scrollbar_area) =
self.content.set_scrollbar_area(scrollbar_area); title_and_scrollbar_area.split_right(ScrollBar::MAX_WIDTH);
// Sending the scrollbar area to the child component.
self.content.set_scrollbar_area(scrollbar_area);
title_area
} else {
title_and_scrollbar_area
};
self.area = title_area; self.area = title_area;
self.content.place(content_area); self.content.place(content_area);
@ -58,7 +83,11 @@ where
} }
fn paint(&mut self) { fn paint(&mut self) {
common::paint_header_left(&self.title, self.area); if self.title_centered {
common::paint_header_centered(&self.title, self.area);
} else {
common::paint_header_left(&self.title, self.area);
}
self.content.paint(); self.content.paint();
} }
} }

View File

@ -1,12 +1,9 @@
use crate::{ use crate::ui::{
time::Duration, component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
ui::{ display::Icon,
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx}, geometry::Rect,
display::Icon, model_tr::theme,
geometry::Rect, util::char_to_string,
model_tr::theme,
util::char_to_string,
},
}; };
use super::{ use super::{
@ -31,7 +28,6 @@ enum ChoiceCategory {
} }
const MAX_PASSPHRASE_LENGTH: usize = 50; const MAX_PASSPHRASE_LENGTH: usize = 50;
const HOLD_DURATION: Duration = Duration::from_secs(1);
const DIGITS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; const DIGITS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const LOWERCASE_LETTERS: [char; 26] = [ const LOWERCASE_LETTERS: [char; 26] = [
@ -115,14 +111,10 @@ impl ChoiceFactoryPassphrase {
// Including accept button on the left and cancel on the very right. // Including accept button on the left and cancel on the very right.
// TODO: could have some icons instead of the shortcut text // TODO: could have some icons instead of the shortcut text
if choice_index == 0 { if choice_index == 0 {
menu_item.set_left_btn(Some( menu_item.set_left_btn(Some(ButtonDetails::text("ACC").with_default_duration()));
ButtonDetails::text("ACC").with_duration(HOLD_DURATION),
));
} }
if choice_index == MENU.len() as u8 - 1 { if choice_index == MENU.len() as u8 - 1 {
menu_item.set_right_btn(Some( menu_item.set_right_btn(Some(ButtonDetails::text("CAN").with_default_duration()));
ButtonDetails::text("CAN").with_duration(HOLD_DURATION),
));
} }
// Including icons for some items. // Including icons for some items.

View File

@ -175,7 +175,7 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
// Optional HoldToConfirm // Optional HoldToConfirm
if hold { if hold {
// TODO: clients might want to set the duration // TODO: clients might want to set the duration
confirm_btn = confirm_btn.map(|btn| btn.with_duration(Duration::from_secs(2))); confirm_btn = confirm_btn.map(|btn| btn.with_default_duration());
} }
let content = ButtonPage::new_str_buf( let content = ButtonPage::new_str_buf(
@ -255,8 +255,7 @@ extern "C" fn confirm_properties(n_args: usize, args: *const Obj, kwargs: *mut M
let mut content = ButtonPage::new_str(paragraphs.into_paragraphs(), theme::BG); let mut content = ButtonPage::new_str(paragraphs.into_paragraphs(), theme::BG);
if hold { if hold {
let confirm_btn = let confirm_btn = Some(ButtonDetails::text("CONFIRM").with_default_duration());
Some(ButtonDetails::text("CONFIRM").with_duration(Duration::from_secs(1)));
content = content.with_confirm_btn(confirm_btn); content = content.with_confirm_btn(confirm_btn);
} }
let obj = LayoutObj::new(Frame::new(title, content))?; let obj = LayoutObj::new(Frame::new(title, content))?;
@ -298,10 +297,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
let btn_layout = ButtonLayout::new( let btn_layout = ButtonLayout::new(
Some(ButtonDetails::cancel_icon()), Some(ButtonDetails::cancel_icon()),
None, None,
Some( Some(ButtonDetails::text("HOLD TO CONFIRM").with_default_duration()),
ButtonDetails::text("HOLD TO CONFIRM")
.with_duration(Duration::from_secs(2)),
),
); );
let btn_actions = ButtonActions::cancel_confirm(); let btn_actions = ButtonActions::cancel_confirm();
Page::<20>::new(btn_layout, btn_actions, Font::MONO) Page::<20>::new(btn_layout, btn_actions, Font::MONO)
@ -342,7 +338,7 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
let btn_layout = ButtonLayout::new( let btn_layout = ButtonLayout::new(
Some(ButtonDetails::cancel_icon()), Some(ButtonDetails::cancel_icon()),
None, None,
Some(ButtonDetails::text("HOLD TO SEND").with_duration(Duration::from_secs(2))), Some(ButtonDetails::text("HOLD TO SEND").with_default_duration()),
); );
let btn_actions = ButtonActions::cancel_confirm(); let btn_actions = ButtonActions::cancel_confirm();
@ -523,8 +519,7 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?; let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
let share_words: Vec<StrBuffer, 24> = iter_into_vec(share_words_obj)?; let share_words: Vec<StrBuffer, 24> = iter_into_vec(share_words_obj)?;
let confirm_btn = let confirm_btn = Some(ButtonDetails::text("HOLD TO CONFIRM").with_default_duration());
Some(ButtonDetails::text("HOLD TO CONFIRM").with_duration(Duration::from_secs(1)));
let obj = LayoutObj::new( let obj = LayoutObj::new(
ButtonPage::new_str(ShareWords::new(share_words), theme::BG) ButtonPage::new_str(ShareWords::new(share_words), theme::BG)
@ -564,7 +559,7 @@ extern "C" fn request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut M
let block = |_args: &[Obj], kwargs: &Map| { let block = |_args: &[Obj], kwargs: &Map| {
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?; let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new()))?; let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new()).with_title_center(true))?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }

View File

@ -66,7 +66,7 @@ async def _continue_recovery_process(ctx: GenericContext) -> Success:
# If we are starting recovery, ask for word count first... # If we are starting recovery, ask for word count first...
# _request_word_count # _request_word_count
await layout.homescreen_dialog( await layout.homescreen_dialog(
ctx, "Select", "Select the number of words in your recovery seed" ctx, "Select", "Select the number of words in your recovery seed."
) )
# ask for the number of words # ask for the number of words
word_count = await layout.request_word_count(ctx, dry_run) word_count = await layout.request_word_count(ctx, dry_run)

View File

@ -419,11 +419,12 @@ async def get_bool(
ctx: wire.GenericContext, ctx: wire.GenericContext,
br_type: str, br_type: str,
title: str, title: str,
data: str, data: str | None = None,
description: str | None = None, description: str | None = None,
verb: str | None = "CONFIRM", verb: str | None = "CONFIRM",
verb_cancel: str | None = "", verb_cancel: str | None = "",
hold: bool = False, hold: bool = False,
reverse: bool = False,
br_code: ButtonRequestType = ButtonRequestType.Other, br_code: ButtonRequestType = ButtonRequestType.Other,
) -> bool: ) -> bool:
result = await interact( result = await interact(
@ -436,7 +437,7 @@ async def get_bool(
verb=verb, verb=verb,
verb_cancel=verb_cancel, verb_cancel=verb_cancel,
hold=hold, hold=hold,
reverse=False, reverse=reverse,
) )
), ),
br_type, br_type,
@ -526,7 +527,7 @@ async def confirm_reset_device(
if show_tutorial: if show_tutorial:
await tutorial(ctx) await tutorial(ctx)
to_show = "By continuing you agree to our terms and conditions.\n\nMore info at trezor.io/tos." to_show = "By continuing you agree to our terms and conditions.\nMore info at trezor.io/tos."
if not recovery: if not recovery:
to_show += "\nUse you backup to recover your wallet." to_show += "\nUse you backup to recover your wallet."

View File

@ -73,11 +73,20 @@ async def continue_recovery(
info_func: Callable | None, info_func: Callable | None,
dry_run: bool, dry_run: bool,
) -> bool: ) -> bool:
# NOTE: no need to implement `info_func`, as it is used only in
# Shamir backup, which is not implemented for TR.
description = text
if subtext:
description += f"\n\n{subtext}"
return await get_bool( return await get_bool(
ctx=ctx, ctx,
title="START RECOVERY", "recovery",
data=f"{text}\n\n{subtext or ''}", "START RECOVERY",
verb="START", None,
br_type="recovery", description,
verb="HOLD TO BEGIN",
hold=True,
reverse=True,
br_code=ButtonRequestType.RecoveryHomepage, br_code=ButtonRequestType.RecoveryHomepage,
) )