1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 13:38:12 +00:00

WIP - designs for smaller screen

This commit is contained in:
grdddj 2022-11-02 14:49:43 +01:00
parent e9f4d91118
commit b4c283230e
40 changed files with 562 additions and 664 deletions

View File

@ -25,8 +25,8 @@ PYOPT = ARGUMENTS.get('PYOPT', '1')
FROZEN = True
if TREZOR_MODEL in ('1', 'R'):
FONT_NORMAL='Font_PixelOperator_Regular_8'
FONT_DEMIBOLD=None
FONT_NORMAL='Font_Unifont_Regular_16'
FONT_DEMIBOLD='Font_Unifont_Bold_16'
FONT_BOLD='Font_PixelOperator_Bold_8'
FONT_MONO='Font_PixelOperatorMono_Regular_8'
if TREZOR_MODEL in ('T', ):

View File

@ -26,8 +26,8 @@ FROZEN = ARGUMENTS.get('TREZOR_EMULATOR_FROZEN', 0)
RASPI = os.getenv('TREZOR_EMULATOR_RASPI') == '1'
if TREZOR_MODEL in ('1', 'R'):
FONT_NORMAL='Font_PixelOperator_Regular_8'
FONT_DEMIBOLD='Font_PixelOperator_Regular_8'
FONT_NORMAL='Font_Unifont_Regular_16'
FONT_DEMIBOLD='Font_Unifont_Bold_16'
FONT_BOLD='Font_PixelOperator_Bold_8'
FONT_MONO='Font_PixelOperatorMono_Regular_8'
if TREZOR_MODEL in ('T', ):

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

BIN
core/assets/model_r/eye.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

View File

@ -193,7 +193,7 @@ void display_init(void) {
#include "background_T.h"
BACKGROUND = IMG_LoadTexture_RW(
RENDERER, SDL_RWFromMem(background_T_jpg, background_T_jpg_len), 0);
#elif defined TREZOR_MODEL_1
#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
#include "background_1.h"
BACKGROUND = IMG_LoadTexture_RW(
RENDERER, SDL_RWFromMem(background_1_jpg, background_1_jpg_len), 0);

View File

@ -29,7 +29,7 @@
#define DISPLAY_RESY 240
#define TREZOR_FONT_BPP 4
#elif defined TREZOR_MODEL_1
#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
#define MAX_DISPLAY_RESX 128
#define MAX_DISPLAY_RESY 64
@ -37,14 +37,6 @@
#define DISPLAY_RESY 64
#define TREZOR_FONT_BPP 1
#elif defined TREZOR_MODEL_R
#define MAX_DISPLAY_RESX 128
#define MAX_DISPLAY_RESY 128
#define DISPLAY_RESX 128
#define DISPLAY_RESY 128
#define TREZOR_FONT_BPP 1
#else
#error Unknown Trezor model
#endif

View File

@ -6,7 +6,7 @@
// - the third, fourth and fifth bytes are advance, bearingX and bearingY of the horizontal metrics of the glyph
// - the rest is packed 1-bit glyph data
/* */ static const uint8_t Font_Unifont_Bold_16_glyph_32[] = { 0, 0, 7, 0, 0 }; // hand-changed to 7 to have 9px space between words
/* */ static const uint8_t Font_Unifont_Bold_16_glyph_32[] = { 0, 0, 8, 0, 0 };
/* ! */ static const uint8_t Font_Unifont_Bold_16_glyph_33[] = { 2, 10, 7, 2, 10, 255, 252, 240 };
/* " */ static const uint8_t Font_Unifont_Bold_16_glyph_34[] = { 6, 4, 7, 0, 12, 207, 60, 209, 0 };
/* # */ static const uint8_t Font_Unifont_Bold_16_glyph_35[] = { 7, 10, 8, 0, 10, 54, 108, 223, 246, 205, 191, 236, 217, 176 };

View File

@ -3,5 +3,8 @@
#if TREZOR_FONT_BPP != 1
#error Wrong TREZOR_FONT_BPP (expected 1)
#endif
#define Font_Unifont_Bold_16_HEIGHT 16
#define Font_Unifont_Bold_16_MAX_HEIGHT 15
#define Font_Unifont_Bold_16_BASELINE 2
extern const uint8_t* const Font_Unifont_Bold_16[126 + 1 - 32];
extern const uint8_t Font_Unifont_Bold_16_glyph_nonprintable[];

View File

@ -3,5 +3,8 @@
#if TREZOR_FONT_BPP != 1
#error Wrong TREZOR_FONT_BPP (expected 1)
#endif
#define Font_Unifont_Regular_16_HEIGHT 16
#define Font_Unifont_Regular_16_MAX_HEIGHT 15
#define Font_Unifont_Regular_16_BASELINE 2
extern const uint8_t* const Font_Unifont_Regular_16[126 + 1 - 32];
extern const uint8_t Font_Unifont_Regular_16_glyph_nonprintable[];

View File

@ -1,8 +1,8 @@
pub mod color;
pub mod font;
pub mod icon;
#[cfg(any(feature = "model_tt", feature = "model_tr"))]
pub mod loader;
pub mod icon;
pub mod font;
pub mod color;
use super::{
constant,
@ -29,11 +29,11 @@ use crate::{
};
// Reexports
pub use color::Color;
pub use font::{Font, Glyph};
pub use icon::{Icon, IconAndName};
#[cfg(any(feature = "model_tt", feature = "model_tr"))]
pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN};
pub use icon::{Icon, IconAndName};
pub use font::{Font, Glyph};
pub use color::Color;
pub fn backlight() -> i32 {
display::backlight(-1)
@ -113,7 +113,8 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
icon_rect(r, toif_data, fg_color, bg_color);
}
/// Display icon at a specified Rectangle, expects already sliced data without header.
/// Display icon at a specified Rectangle, expects already sliced data without
/// header.
pub fn icon_rect(r: Rect, toif_data: &[u8], fg_color: Color, bg_color: Color) {
display::icon(
r.x0,
@ -211,6 +212,7 @@ pub fn rect_outline_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u
if radius == 1 {
rect_fill_rounded(r, fg_color, bg_color, 1);
rect_fill(inner_r, bg_color);
rect_fill_corners(inner_r, fg_color);
} else if radius == 2 {
rect_fill_rounded(r, fg_color, bg_color, 2);
rect_fill_rounded(inner_r, bg_color, fg_color, 1);

View File

@ -309,6 +309,10 @@ impl Rect {
self.bottom_left().center(self.bottom_right())
}
pub const fn top_center(&self) -> Point {
self.top_left().center(self.top_right())
}
pub const fn left_center(&self) -> Point {
self.bottom_left().center(self.top_left())
}

View File

@ -3,12 +3,13 @@ use crate::{
ui::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
geometry::Rect,
util::char_to_string,
},
};
use super::{
choice_item::BigCharacterChoiceItem, ButtonDetails, ButtonLayout, ChangingTextLine,
ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, TextChoiceItem,
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
ChoicePageMsg,
};
use heapless::{String, Vec};
@ -16,14 +17,14 @@ pub enum Bip39EntryMsg {
ResultWord(String<15>),
}
const CURRENT_LETTERS_ROW: i32 = 25;
const MAX_LENGTH: usize = 10;
const MAX_CHOICE_LENGTH: usize = 26;
/// Offer words when there will be fewer of them than this
const OFFER_WORDS_THRESHOLD: usize = 10;
const PROMPT: &str = "_";
struct ChoiceFactoryBIP39 {
// TODO: replace these Vecs by iterators somehow?
letter_choices: Option<Vec<char, MAX_CHOICE_LENGTH>>,
@ -52,13 +53,14 @@ impl ChoiceFactoryBIP39 {
fn get_word_item(&self, choice_index: u8) -> ChoiceItem {
if let Some(word_choices) = &self.word_choices {
let word = word_choices[choice_index as usize];
let choice = TextChoiceItem::new(word, ButtonLayout::default_three_icons());
let mut word_item = ChoiceItem::Text(choice);
let mut word_item = ChoiceItem::new(word, ButtonLayout::default_three_icons());
// Adding BIN leftmost button and removing the rightmost one.
// Adding BIN leftmost button.
if choice_index == 0 {
word_item.set_left_btn(Some(ButtonDetails::bin_icon()));
} else if choice_index as usize == word_choices.len() - 1 {
}
// Removing the rightmost button.
if choice_index as usize == word_choices.len() - 1 {
word_item.set_right_btn(None);
}
@ -75,14 +77,17 @@ impl ChoiceFactoryBIP39 {
// user-friendly)
if let Some(letter_choices) = &self.letter_choices {
let letter = letter_choices[choice_index as usize];
let letter_choice =
BigCharacterChoiceItem::new(letter, ButtonLayout::default_three_icons());
let mut letter_item = ChoiceItem::BigCharacter(letter_choice);
let mut letter_item = ChoiceItem::new(
char_to_string::<1>(letter),
ButtonLayout::default_three_icons(),
);
// Adding BIN leftmost button and removing the rightmost one.
// Adding BIN leftmost button.
if choice_index == 0 {
letter_item.set_left_btn(Some(ButtonDetails::bin_icon()));
} else if choice_index as usize == letter_choices.len() - 1 {
}
// Removing the rightmost button.
if choice_index as usize == letter_choices.len() - 1 {
letter_item.set_right_btn(None);
}
@ -131,8 +136,8 @@ impl Bip39Entry {
let choices = ChoiceFactoryBIP39::letters(letter_choices.clone());
Self {
choice_page: ChoicePage::new(choices),
chosen_letters: Child::new(ChangingTextLine::center_mono(String::new())),
choice_page: ChoicePage::new(choices).with_incomplete(),
chosen_letters: Child::new(ChangingTextLine::center_mono(String::from(PROMPT))),
letter_choices,
textbox: TextBox::empty(),
offer_words: false,
@ -159,7 +164,7 @@ impl Bip39Entry {
}
fn update_chosen_letters(&mut self, ctx: &mut EventCtx) {
let text = build_string!({ MAX_LENGTH + 1 }, self.textbox.content(), "_");
let text = build_string!({ MAX_LENGTH + 1 }, self.textbox.content(), PROMPT);
self.chosen_letters.inner_mut().update_text(text);
self.chosen_letters.request_complete_repaint(ctx);
}

View File

@ -233,10 +233,18 @@ where
text_color,
background_color,
);
display::rect_fill_corners(area_to_fill, theme::BG);
} else if style.with_outline {
if background_color == theme::BG {
display::rect_outline_rounded(area, text_color, background_color, 2);
} else {
display::rect_fill(area, background_color)
// With inverse colors having just radius of one, `rect_outline_rounded`
// is not suitable for inverse colors.
display::rect_fill(area, background_color);
display::rect_fill_corners(area, theme::BG);
}
} else {
display::rect_fill(area, background_color);
}
match &self.content {

View File

@ -23,6 +23,7 @@ enum ButtonState {
}
pub enum ButtonControllerMsg {
Pressed(ButtonPos),
Triggered(ButtonPos),
}
@ -275,16 +276,18 @@ impl<T: Clone + AsRef<str>> Component for ButtonController<T> {
let (new_state, event) = match self.state {
ButtonState::Nothing => match button {
ButtonEvent::ButtonPressed(which) => {
match which {
let event = match which {
PhysicalButton::Left => {
self.left_btn.hold_started(ctx);
Some(ButtonControllerMsg::Pressed(ButtonPos::Left))
}
PhysicalButton::Right => {
self.right_btn.hold_started(ctx);
Some(ButtonControllerMsg::Pressed(ButtonPos::Right))
}
_ => {}
}
(ButtonState::OneDown(which), None)
_ => None,
};
(ButtonState::OneDown(which), event)
}
_ => (self.state, None),
},
@ -313,7 +316,10 @@ impl<T: Clone + AsRef<str>> Component for ButtonController<T> {
ButtonEvent::ButtonPressed(b) if b != which_down => {
self.middle_hold_started(ctx);
(ButtonState::BothDown, None)
(
ButtonState::BothDown,
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
)
}
_ => (self.state, None),
},

View File

@ -3,7 +3,7 @@ use crate::ui::{
geometry::Rect,
};
use super::{theme, ButtonController, ButtonControllerMsg, ButtonPos, ChoiceItem, ChoiceItemAPI};
use super::{theme, ButtonController, ButtonControllerMsg, ButtonPos, ChoiceItem};
pub enum ChoicePageMsg {
Choice(u8),
@ -11,7 +11,8 @@ pub enum ChoicePageMsg {
RightMost,
}
const MIDDLE_ROW: i32 = 72;
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
const DEFAULT_Y_BASELINE: i16 = 20;
/// Interface for a specific component efficiently giving
/// `ChoicePage` all the information it needs to render
@ -48,7 +49,18 @@ where
pad: Pad,
buttons: Child<ButtonController<&'static str>>,
page_counter: u8,
/// How many pixels from top should we render the items.
y_baseline: i16,
/// How many pixels are between the items.
items_distance: i16,
/// Whether the choice page is "infinite" (carousel).
is_carousel: bool,
/// Whether we should show items on left/right even when they cannot
/// be painted entirely (they would be cut off).
show_incomplete: bool,
/// Whether the middle selected item should be painted with
/// inverse colors - black on white.
inverse_selected_item: bool,
}
impl<F> ChoicePage<F>
@ -63,7 +75,11 @@ where
pad: Pad::with_background(theme::BG),
buttons: Child::new(ButtonController::new(initial_btn_layout)),
page_counter: 0,
y_baseline: DEFAULT_Y_BASELINE,
items_distance: DEFAULT_ITEMS_DISTANCE,
is_carousel: false,
show_incomplete: false,
inverse_selected_item: false,
}
}
@ -79,9 +95,30 @@ where
self
}
/// Show incomplete items, even when they cannot render in their entirety.
pub fn with_incomplete(mut self) -> Self {
self.show_incomplete = true;
self
}
/// Adjust the horizontal baseline from the top of placement.
pub fn with_y_baseline(mut self, y_baseline: i16) -> Self {
self.y_baseline = y_baseline;
self
}
/// Adjust the distance between the items.
pub fn with_items_distance(mut self, items_distance: i16) -> Self {
self.items_distance = items_distance;
self
}
/// Resetting the component, which enables reusing the same instance
/// for multiple choice categories.
///
/// Used for example in passphrase, where there are multiple categories of
/// characters.
///
/// NOTE: from the client point of view, it would also be an option to
/// always create a new instance with fresh setup, but I could not manage to
/// properly clean up the previous instance - it would still be shown on
@ -97,8 +134,8 @@ where
if reset_page_counter {
self.page_counter = 0;
}
self.update(ctx);
self.is_carousel = is_carousel;
self.update(ctx);
}
/// Navigating to the chosen page index.
@ -107,24 +144,26 @@ where
self.update(ctx);
}
/// Display current, previous and next choice according to
/// Display current, previous and next choices according to
/// the current ChoiceItem.
fn paint_choices(&mut self) {
// Performing the appropriate `paint_XXX()` for the main choice
// and two adjacent choices when present
// In case of carousel mode, also showing the ones from other end.
self.show_current_choice();
let available_area = self.pad.area.split_top(self.y_baseline).0;
if self.has_previous_choice() {
self.show_previous_choice();
} else if self.is_carousel {
self.show_last_choice_on_left();
// Drawing the current item in the middle.
self.show_current_choice(available_area);
// Getting the remaining left and right areas.
let (left_area, _center_area, right_area) =
available_area.split_center(self.current_choice().width_center());
// Possibly drawing on the left side.
if self.has_previous_choice() || self.is_carousel {
self.show_left_choices(left_area);
}
if self.has_next_choice() {
self.show_next_choice();
} else if self.is_carousel {
self.show_first_choice_on_right();
// Possibly drawing on the right side.
if self.has_next_choice() || self.is_carousel {
self.show_right_choices(right_area);
}
}
@ -140,62 +179,139 @@ where
ctx.request_paint();
}
/// Index of the last page.
fn last_page_index(&self) -> u8 {
self.choices.count() as u8 - 1
}
/// Whether there is a previous choice (on the left).
pub fn has_previous_choice(&self) -> bool {
self.page_counter > 0
}
/// Whether there is a next choice (on the right).
pub fn has_next_choice(&self) -> bool {
self.page_counter < self.last_page_index()
}
/// Gets choice at the current page index.
fn current_choice(&self) -> ChoiceItem {
self.get_choice(self.page_counter)
}
/// Gets choice at the given page index.
fn get_choice(&self, index: u8) -> ChoiceItem {
self.choices.get(index)
}
fn show_current_choice(&self) {
self.current_choice().paint_center();
/// Display the current choice in the middle.
fn show_current_choice(&mut self, area: Rect) {
self.current_choice()
.paint_center(area, self.inverse_selected_item);
// Color inversion is just one-time thing.
if self.inverse_selected_item {
self.inverse_selected_item = false;
}
}
fn show_previous_choice(&self) {
self.get_choice(self.page_counter - 1).paint_left();
/// Display all the choices fitting on the left side.
/// Going as far as possible.
fn show_left_choices(&self, area: Rect) {
let mut page_index = self.page_counter as i16 - 1;
let mut x_offset = 0;
loop {
// Breaking out of the loop if we exhausted left items
// and the carousel mode is not enabled.
if page_index < 0 {
if self.is_carousel {
// Moving to the last page.
page_index = self.last_page_index() as i16;
} else {
break;
}
}
fn show_next_choice(&self) {
self.get_choice(self.page_counter + 1).paint_right();
let current_choice = self.get_choice(page_index as u8);
let current_area = area.split_right(x_offset + self.items_distance).0;
// When the item does not fit, we stop.
// Rendering the item anyway if the incomplete items are allowed.
if !current_choice.fits(current_area) {
if self.show_incomplete {
current_choice.paint_left(current_area);
}
break;
}
fn show_last_choice_on_left(&self) {
self.get_choice(self.last_page_index()).paint_left();
// Rendering the item.
current_choice.paint_left(current_area);
// Updating loop variables.
x_offset += current_choice.width_side() + self.items_distance;
page_index -= 1;
}
}
fn show_first_choice_on_right(&self) {
self.get_choice(0).paint_right();
/// Display all the choices fitting on the right side.
/// Going as far as possible.
fn show_right_choices(&self, area: Rect) {
let mut page_index = self.page_counter + 1;
let mut x_offset = 3; // starts with a little offset to account for the middle highlight
loop {
// Breaking out of the loop if we exhausted right items
// and the carousel mode is not enabled.
if page_index > self.last_page_index() {
if self.is_carousel {
// Moving to the first page.
page_index = 0;
} else {
break;
}
}
let current_choice = self.get_choice(page_index);
let current_area = area.split_left(x_offset + self.items_distance).1;
// When the item does not fit, we stop.
// Rendering the item anyway if the incomplete items are allowed.
if !current_choice.fits(current_area) {
if self.show_incomplete {
current_choice.paint_right(current_area);
}
break;
}
// Rendering the item.
current_choice.paint_right(current_area);
// Updating loop variables.
x_offset += current_choice.width_side() + self.items_distance;
page_index += 1;
}
}
/// Decrease the page counter to the previous page.
fn decrease_page_counter(&mut self) {
self.page_counter -= 1;
}
/// Advance page counter to the next page.
fn increase_page_counter(&mut self) {
self.page_counter += 1;
}
/// Set page to the first one.
fn page_counter_to_zero(&mut self) {
self.page_counter = 0;
}
/// Set page to the last one.
fn page_counter_to_max(&mut self) {
self.page_counter = self.last_page_index();
}
/// Get current page counter.
pub fn page_index(&self) -> u8 {
self.page_counter
}
@ -238,6 +354,7 @@ where
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let button_event = self.buttons.event(ctx, event);
// Button was "triggered" - released. Doing the appropriate action.
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
match pos {
ButtonPos::Left => {
@ -277,6 +394,12 @@ where
}
}
};
// The middle button was "pressed", highlighting the current choice by color
// inversion.
if let Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)) = button_event {
self.inverse_selected_item = true;
self.clear(ctx);
};
None
}

View File

@ -1,338 +1,172 @@
use crate::ui::{geometry::Point, display::Font, util::char_to_string};
use crate::ui::{
display::{rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon},
geometry::{Offset, Rect},
model_tr::theme,
};
use heapless::String;
use super::{
common::{display, display_center, display_right},
common::{display, display_inverse, display_right},
ButtonDetails, ButtonLayout,
};
const MIDDLE_ROW: i16 = 61;
const LEFT_COL: i16 = 1;
const MIDDLE_COL: i16 = 64;
const RIGHT_COL: i16 = 127;
/// Helper to unite the row height.
fn row_height() -> i16 {
// It never reaches the maximum height
Font::NORMAL.line_height() - 4
}
/// Component that can be used as a choice item.
/// Allows to have a choice of anything that can be painted on screen.
///
/// Controls the painting of the current, previous and next item
/// through `paint_XXX()` methods.
/// Defines the behavior of all three buttons through `btn_XXX` attributes.
///
/// Possible implementations:
/// - [x] `TextChoiceItem` - for regular text
/// - [x] `MultilineTextChoiceItem` - for multiline text
/// - [x] `BigCharacterChoiceItem` - for one big character
/// - [ ] `IconChoiceItem` - for showing icons
/// - [ ] `JustCenterChoice` - paint_left() and paint_right() show nothing
/// - [ ] `LongStringsChoice` - paint_left() and paint_right() show ellipsis
pub trait ChoiceItemAPI {
fn paint_center(&mut self);
fn paint_left(&mut self);
fn paint_right(&mut self);
fn btn_layout(&self) -> ButtonLayout<&'static str>;
}
// TODO: consider having
// pub trait ChoiceItemOperations {}
// TODO: consider storing all the text components as `T: AsRef<str>`
// Tried, but it makes the code unnecessarily messy with all the <T>
// definitions, which needs to be added to all the components using it.
/// Storing all the possible implementations of `ChoiceItemAPI`.
/// Done like this as we want to use multiple different choice pages
/// at the same time in `ChoicePage` - for example Multiline and BigLetters
/// Simple string component used as a choice item.
#[derive(Clone)]
pub enum ChoiceItem {
Text(TextChoiceItem),
MultilineText(MultilineTextChoiceItem),
BigCharacter(BigCharacterChoiceItem),
pub struct ChoiceItem {
text: String<50>,
icon: Option<Icon>,
btn_layout: ButtonLayout<&'static str>,
font: Font,
}
impl ChoiceItem {
// TODO: can we somehow avoid the repetitions here?
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<&'static str>>) {
match self {
ChoiceItem::Text(item) => item.btn_layout.btn_left = btn_left,
ChoiceItem::MultilineText(item) => item.btn_layout.btn_left = btn_left,
ChoiceItem::BigCharacter(item) => item.btn_layout.btn_left = btn_left,
}
}
pub fn set_middle_btn(&mut self, btn_middle: Option<ButtonDetails<&'static str>>) {
match self {
ChoiceItem::Text(item) => item.btn_layout.btn_middle = btn_middle,
ChoiceItem::MultilineText(item) => item.btn_layout.btn_middle = btn_middle,
ChoiceItem::BigCharacter(item) => item.btn_layout.btn_middle = btn_middle,
}
}
pub fn set_right_btn(&mut self, btn_right: Option<ButtonDetails<&'static str>>) {
match self {
ChoiceItem::Text(item) => item.btn_layout.btn_right = btn_right,
ChoiceItem::MultilineText(item) => item.btn_layout.btn_right = btn_right,
ChoiceItem::BigCharacter(item) => item.btn_layout.btn_right = btn_right,
}
}
pub fn set_text(&mut self, text: String<50>) {
match self {
ChoiceItem::Text(item) => item.text = text,
ChoiceItem::MultilineText(item) => item.text = text,
ChoiceItem::BigCharacter(_) => {
panic!("No text setting for BigCharacter")
}
}
}
}
impl ChoiceItemAPI for ChoiceItem {
fn paint_center(&mut self) {
match self {
ChoiceItem::Text(item) => item.paint_center(),
ChoiceItem::MultilineText(item) => item.paint_center(),
ChoiceItem::BigCharacter(item) => item.paint_center(),
}
}
fn paint_left(&mut self) {
match self {
ChoiceItem::Text(item) => item.paint_left(),
ChoiceItem::MultilineText(item) => item.paint_left(),
ChoiceItem::BigCharacter(item) => item.paint_left(),
}
}
fn paint_right(&mut self) {
match self {
ChoiceItem::Text(item) => item.paint_right(),
ChoiceItem::MultilineText(item) => item.paint_right(),
ChoiceItem::BigCharacter(item) => item.paint_right(),
}
}
fn btn_layout(&self) -> ButtonLayout<&'static str> {
match self {
ChoiceItem::Text(item) => item.btn_layout(),
ChoiceItem::MultilineText(item) => item.btn_layout(),
ChoiceItem::BigCharacter(item) => item.btn_layout(),
}
}
}
/// Simple string component used as a choice item.
#[derive(Clone)]
pub struct TextChoiceItem {
pub text: String<50>,
pub btn_layout: ButtonLayout<&'static str>,
}
impl TextChoiceItem {
pub fn new<T>(text: T, btn_layout: ButtonLayout<&'static str>) -> Self
where
T: AsRef<str>,
{
Self {
text: String::from(text.as_ref()),
icon: None,
btn_layout,
}
}
}
impl ChoiceItemAPI for TextChoiceItem {
fn paint_center(&mut self) {
// Displaying the center choice lower than the rest,
// to make it more clear this is the current choice
// (and also the left and right ones do not collide with it)
display_center(
Point::new(MIDDLE_COL, MIDDLE_ROW + row_height()),
&self.text,
Font::NORMAL,
);
}
fn paint_left(&mut self) {
display(
Point::new(LEFT_COL, MIDDLE_ROW),
&self.text,
Font::NORMAL,
);
}
fn paint_right(&mut self) {
display_right(
Point::new(RIGHT_COL, MIDDLE_ROW),
&self.text,
Font::NORMAL,
);
}
fn btn_layout(&self) -> ButtonLayout<&'static str> {
self.btn_layout.clone()
}
}
/// Multiline string component used as a choice item.
///
/// Lines are delimited by '\n' character, unless specified explicitly.
#[derive(Clone)]
pub struct MultilineTextChoiceItem {
// Arbitrary chosen. TODO: agree on this
pub text: String<50>,
delimiter: char,
pub btn_layout: ButtonLayout<&'static str>,
}
impl MultilineTextChoiceItem {
pub fn new(text: String<50>, btn_layout: ButtonLayout<&'static str>) -> Self {
Self {
text,
delimiter: '\n',
btn_layout,
font: theme::FONT_CHOICE_ITEMS,
}
}
/// Allows for changing the line delimiter to arbitrary char.
pub fn use_delimiter(mut self, delimiter: char) -> Self {
self.delimiter = delimiter;
/// Allows to add the icon.
pub fn with_icon(mut self, icon: Icon) -> Self {
self.icon = Some(icon);
self
}
}
// TODO: Make all the text be centered vertically - account for amount of lines.
impl ChoiceItemAPI for MultilineTextChoiceItem {
fn paint_center(&mut self) {
// Displaying the center choice lower than the rest,
// to make it more clear this is the current choice
for (index, line) in self.text.split(self.delimiter).enumerate() {
let offset = MIDDLE_ROW + index as i16 * row_height() + row_height();
display_center(Point::new(MIDDLE_COL, offset), &line, Font::NORMAL);
/// Allows to change the font.
pub fn with_font(mut self, font: Font) -> Self {
self.font = font;
self
}
/// Getting the text width in pixels.
pub fn text_width(&self) -> i16 {
self.font.text_width(&self.text)
}
/// Getting the overall width in pixels when displayed in center.
/// That means both the icon and text will be shown.
pub fn width_center(&self) -> i16 {
let icon_width = if let Some(icon) = self.icon {
icon.width() + 2
} else {
0
};
self.text_width() + icon_width
}
/// Getting the non-central width in pixels.
/// It will show an icon if defined, otherwise the text, not both.
pub fn width_side(&self) -> i16 {
if let Some(icon) = self.icon {
icon.width()
} else {
self.text_width()
}
}
fn paint_left(&mut self) {
for (index, line) in self.text.split(self.delimiter).enumerate() {
let offset = MIDDLE_ROW + index as i16 * row_height();
display(Point::new(LEFT_COL, offset), &line, Font::NORMAL);
}
/// Whether the whole item fits into the given rectangle.
pub fn fits(&self, rect: Rect) -> bool {
self.width_side() <= rect.width()
}
fn paint_right(&mut self) {
for (index, line) in self.text.split(self.delimiter).enumerate() {
let offset = MIDDLE_ROW + index as i16 * row_height();
display_right(Point::new(RIGHT_COL, offset), &line, Font::NORMAL);
}
}
fn btn_layout(&self) -> ButtonLayout<&'static str> {
self.btn_layout.clone()
}
}
/// Choice item displaying single characters in BIG font.
/// Middle choice is magnified 4 times, left and right 2 times.
#[derive(Clone)]
pub struct BigCharacterChoiceItem {
pub ch: char,
pub btn_layout: ButtonLayout<&'static str>,
}
impl BigCharacterChoiceItem {
pub fn new(ch: char, btn_layout: ButtonLayout<&'static str>) -> Self {
Self { ch, btn_layout }
}
/// Taking the first character from the `text`.
pub fn from_str<T>(text: T, btn_layout: ButtonLayout<&'static str>) -> Self
where
T: AsRef<str>,
{
Self {
ch: text.as_ref().chars().next().unwrap(),
btn_layout,
}
}
fn _paint_char(&mut self, baseline: Point) {
display(
baseline,
&char_to_string::<1>(self.ch),
Font::NORMAL,
/// Draws highlight around this choice item.
/// Must be called before the item is drawn, otherwise it will
/// cover the item.
pub fn paint_rounded_highlight(&self, area: Rect, inverse: bool) {
let bound = 3;
let left_bottom =
area.bottom_center() + Offset::new(-self.width_center() / 2 - bound, bound + 1);
let outline_size = Offset::new(
self.width_center() + 2 * bound,
self.font.text_height() + 2 * bound - 3, // -3 because font is actually smaller
);
let outline = Rect::from_bottom_left_and_size(left_bottom, outline_size);
if inverse {
rect_fill(outline, theme::FG);
rect_fill_corners(outline, theme::BG);
} else {
rect_outline_rounded(outline, theme::FG, theme::BG, 1);
}
}
}
/// Painting the item as the main choice in the middle.
/// Showing both the icon and text, if the icon is available.
pub fn paint_center(&self, area: Rect, inverse: bool) {
self.paint_rounded_highlight(area, inverse);
impl ChoiceItemAPI for BigCharacterChoiceItem {
fn paint_center(&mut self) {
self._paint_char(Point::new(MIDDLE_COL - 12, MIDDLE_ROW + 9));
let mut baseline = area.bottom_center() + Offset::new(-self.width_center() / 2, 0);
if let Some(icon) = self.icon {
let fg_color = if inverse { theme::BG } else { theme::FG };
let bg_color = if inverse { theme::FG } else { theme::BG };
icon.draw_bottom_left(baseline, fg_color, bg_color);
baseline = baseline + Offset::new(icon.width() + 2, 0);
}
if inverse {
display_inverse(baseline, &self.text, self.font);
} else {
display(baseline, &self.text, self.font);
}
}
fn paint_left(&mut self) {
self._paint_char(Point::new(LEFT_COL, MIDDLE_ROW));
/// Painting the item as a choice on the left side from center.
/// Showing only the icon, if available, otherwise the text.
pub fn paint_left(&self, area: Rect) {
if let Some(icon) = self.icon {
icon.draw_bottom_right(area.bottom_right(), theme::FG, theme::BG);
} else {
display_right(area.bottom_right(), &self.text, self.font);
}
}
fn paint_right(&mut self) {
self._paint_char(Point::new(RIGHT_COL - 12, MIDDLE_ROW));
/// Painting the item as a choice on the right side from center.
/// Showing only the icon, if available, otherwise the text.
pub fn paint_right(&self, area: Rect) {
if let Some(icon) = self.icon {
icon.draw_bottom_left(area.bottom_left(), theme::FG, theme::BG);
} else {
display(area.bottom_left(), &self.text, self.font);
}
}
fn btn_layout(&self) -> ButtonLayout<&'static str> {
/// Getting current button layout.
pub fn btn_layout(&self) -> ButtonLayout<&'static str> {
self.btn_layout.clone()
}
/// Setting left button.
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<&'static str>>) {
self.btn_layout.btn_left = btn_left;
}
/// Setting middle button.
pub fn set_middle_btn(&mut self, btn_middle: Option<ButtonDetails<&'static str>>) {
self.btn_layout.btn_middle = btn_middle;
}
/// Setting right button.
pub fn set_right_btn(&mut self, btn_right: Option<ButtonDetails<&'static str>>) {
self.btn_layout.btn_right = btn_right;
}
/// Changing the text.
pub fn set_text(&mut self, text: String<50>) {
self.text = text;
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ChoiceItem {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ChoiceItem");
match self {
ChoiceItem::Text(item) => item.trace(t),
ChoiceItem::MultilineText(item) => item.trace(t),
ChoiceItem::BigCharacter(item) => item.trace(t),
}
t.close();
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for TextChoiceItem {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("TextChoiceItem");
t.content_flag();
t.string(&self.text);
t.content_flag();
t.close();
}
}
#[cfg(feature = "ui_debug")]
use crate::ui::util;
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for MultilineTextChoiceItem {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("MultilineTextChoiceItem");
t.content_flag();
t.string(&self.text);
t.content_flag();
t.field("delimiter", &(util::char_to_string::<1>(self.delimiter)));
t.close();
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for BigCharacterChoiceItem {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("BigCharacterChoiceItem");
t.content_flag();
t.string(&util::char_to_string::<1>(self.ch));
t.content_flag();
t.close();
}
}

View File

@ -1,30 +1,22 @@
use crate::ui::{
display::{self, Font, Icon},
geometry::{Offset, Point},
model_tr::constant,
geometry::{Offset, Point, Rect},
};
use heapless::String;
use super::theme;
/// Display header text.
pub fn display_header<T: AsRef<str>>(baseline: Point, text: T) {
// TODO: make this centered?
display::text(
baseline,
text.as_ref(),
theme::FONT_HEADER,
theme::FG,
theme::BG,
);
}
/// Display bold white text on black background
/// Display white text on black background
pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
display::text(baseline, text.as_ref(), font, theme::FG, theme::BG);
}
/// Display black text on white background
pub fn display_inverse<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
display::text(baseline, text.as_ref(), font, theme::BG, theme::FG);
}
/// Display white text on black background,
/// centered around a baseline Point
pub fn display_center<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
@ -76,23 +68,19 @@ pub fn display_secret_center_top<T: AsRef<str>>(secret: T, offset_from_top: i16)
}
}
/// Display title and possible subtitle together with a dotted line spanning
/// the entire width.
/// Display title/header centered at the top of the given area.
/// Returning the painted height of the whole header.
pub fn paint_header<T: AsRef<str>>(top_left: Point, title: T, subtitle: Option<T>) -> i16 {
pub fn paint_header_centered<T: AsRef<str>>(title: T, area: Rect) -> i16 {
let text_heigth = theme::FONT_HEADER.text_height();
let title_baseline = top_left + Offset::y(text_heigth);
display_header(title_baseline, title);
// Optionally painting the subtitle as well
// (and offsetting the dotted line in that case)
let mut dotted_line_offset = text_heigth + 2;
if let Some(subtitle) = subtitle {
dotted_line_offset += text_heigth;
display_header(title_baseline + Offset::y(text_heigth), subtitle);
}
let line_start = top_left + Offset::y(dotted_line_offset);
display::dotted_line_horizontal(line_start, constant::WIDTH, theme::FG, 2);
dotted_line_offset
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.

View File

@ -2,7 +2,7 @@ use crate::{
micropython::buffer::StrBuffer,
ui::{
component::{Child, Component, Event, EventCtx, Pad},
geometry::{Point, Rect},
geometry::Rect,
},
};
@ -62,8 +62,7 @@ where
if get_new_page {
self.current_page = self.pages.get(self.page_counter);
}
let content_area = self.content_area;
self.current_page.place(content_area);
self.current_page.place(self.content_area);
self.set_buttons(ctx);
self.clear(ctx);
}
@ -203,12 +202,13 @@ where
fn paint(&mut self) {
// TODO: might put horizontal scrollbar at the top right
// (not compatible with longer/centered titles)
self.pad.paint();
self.buttons.paint();
if let Some(title) = &self.common_title {
common::paint_header(Point::zero(), title, None);
common::paint_header_centered(title, self.content_area);
}
self.current_page.paint();
self.buttons.paint();
}
}

View File

@ -1,11 +1,11 @@
use crate::{
micropython::{buffer::StrBuffer},
micropython::buffer::StrBuffer,
ui::{
component::Paginate,
display::{Font, Icon, IconAndName},
geometry::{Offset, Rect},
model_tr::theme,
util::ResultExt
util::ResultExt,
},
};
@ -68,9 +68,13 @@ pub struct Page<const M: usize> {
// For `layout.rs`
impl<const M: usize> Page<M> {
pub fn new(btn_layout: ButtonLayout<&'static str>, btn_actions: ButtonActions) -> Self {
pub fn new(
btn_layout: ButtonLayout<&'static str>,
btn_actions: ButtonActions,
initial_text_font: Font,
) -> Self {
let style = TextStyle::new(
Font::NORMAL,
initial_text_font,
theme::FG,
theme::BG,
theme::FG,
@ -200,6 +204,10 @@ impl<const M: usize> Page<M> {
self.font(Font::NORMAL).text(text)
}
pub fn text_mono(self, text: StrBuffer) -> Self {
self.font(Font::MONO).text(text)
}
pub fn text_bold(self, text: StrBuffer) -> Self {
self.font(Font::BOLD).text(text)
}

View File

@ -1,11 +1,6 @@
//! Mostly copy-pasted stuff from ui/component/text,
//! but with small modifications.
//! It is really mostly changing Op::Text(&'a str) to Op::Text(String<100>),
//! having self.ops as Vec<Op, 30> and changes revolving around it.
//! Even if some stuff could be reused now, I copy-pasted it anyway, as this
//! extension for Icons, Offsets, etc. should no longer live in
//! ui/component/text, and so they can be freely removed (as they are here as
//! well).
//! (support for more Ops like icon drawing or arbitrary offsets)
use crate::{
micropython::buffer::StrBuffer,
@ -33,7 +28,7 @@ impl ToDisplay {
}
}
/// Operations that can be done on FormattedText.
/// Operations that can be done on the screen.
#[derive(Clone)]
pub enum Op {
/// Render text with current color and font.
@ -151,26 +146,11 @@ impl TextLayout {
}
}
pub fn with_bounds(mut self, bounds: Rect) -> Self {
self.bounds = bounds;
self
}
/// Baseline `Point` where we are starting to draw the text.
pub fn initial_cursor(&self) -> Point {
self.bounds.top_left() + Offset::y(self.style.text_font.text_height() + self.padding_top)
}
/// Trying to fit the content on the current screen.
pub fn fit_text(&self, text: &str) -> LayoutFit {
self.layout_text(text, &mut self.initial_cursor(), &mut TextNoOp)
}
/// Draw as much text as possible on the current screen.
pub fn render_text(&self, text: &str) {
self.layout_text(text, &mut self.initial_cursor(), &mut TextRenderer);
}
/// Y coordinate of the bottom of the available space/bounds
pub fn bottom_y(&self) -> i16 {
(self.bounds.y1 - self.padding_bottom).max(self.bounds.y0)
@ -433,7 +413,6 @@ impl TextLayout {
cursor.x += icon.width() as i16;
LayoutFit::Fitting {
// TODO: how to handle this? It could collide with "skip_first_n_bytes"
processed_chars: 1,
height: 0, // it should just draw on one line
}
@ -467,9 +446,6 @@ impl LayoutFit {
}
}
// TODO: LayoutSink could support even things like drawing icons
// or making custom x or y offsets from any position
/// Visitor for text segment operations.
/// Defines responses for certain kind of events encountered
/// when processing the content.

View File

@ -4,12 +4,10 @@ use crate::ui::{
geometry::{Insets, Rect},
};
/// Component for holding another component and displaying
/// a title and optionally a subtitle describing that child component.
/// Component for holding another component and displaying a title.
pub struct Frame<T, U> {
area: Rect,
title: U,
subtitle: Option<U>,
content: Child<T>,
}
@ -18,10 +16,9 @@ where
T: Component,
U: AsRef<str>,
{
pub fn new(title: U, subtitle: Option<U>, content: T) -> Self {
pub fn new(title: U, content: T) -> Self {
Self {
title,
subtitle,
area: Rect::zero(),
content: Child::new(content),
}
@ -40,11 +37,10 @@ where
type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect {
// Depending on whether there is subtitle or not
let title_space = if self.subtitle.is_some() { 12 } else { 4 };
const TITLE_SPACE: i16 = 4;
let (title_area, content_area) = 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));
self.area = title_area;
self.content.place(content_area);
@ -56,7 +52,7 @@ where
}
fn paint(&mut self) {
common::paint_header(self.area.top_left(), &self.title, self.subtitle.as_ref());
common::paint_header_centered(&self.title, self.area);
self.content.paint();
}
}
@ -70,9 +66,6 @@ where
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Frame");
t.title(self.title.as_ref());
if let Some(ref subtitle) = self.subtitle {
t.title(subtitle.as_ref());
}
t.field("content", &self.content);
t.close();
}

View File

@ -32,7 +32,7 @@ pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
pub use button_controller::{ButtonController, ButtonControllerMsg};
pub use changing_text::ChangingTextLine;
pub use choice::{ChoiceFactory, ChoicePage, ChoicePageMsg};
pub use choice_item::{ChoiceItem, ChoiceItemAPI, MultilineTextChoiceItem, TextChoiceItem};
pub use choice_item::ChoiceItem;
pub use dialog::{Dialog, DialogMsg};
pub use flow::{Flow, FlowMsg};
pub use flow_pages::{FlowPages, Page};

View File

@ -2,13 +2,16 @@ use crate::{
time::Duration,
ui::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
display::Icon,
geometry::Rect,
model_tr::theme,
util::char_to_string,
},
};
use super::{
choice_item::BigCharacterChoiceItem, ButtonDetails, ButtonLayout, ChangingTextLine,
ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, MultilineTextChoiceItem, TextChoiceItem,
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
ChoicePageMsg,
};
use heapless::String;
@ -44,9 +47,9 @@ const SPECIAL_SYMBOLS: [char; 30] = [
'{', '}', ',', '\'', '`', ';', '"', '~', '$', '^', '=',
];
const MENU_LENGTH: usize = 6;
const DEL_INDEX: usize = MENU_LENGTH - 1;
const DELETE_INDEX: usize = MENU_LENGTH - 1;
const SHOW_INDEX: usize = MENU_LENGTH - 2;
const MENU: [&str; MENU_LENGTH] = ["abc", "ABC", "123", "*#_", "SHOW PASS", "DEL LAST CHAR"];
const MENU: [&str; MENU_LENGTH] = ["abc", "ABC", "123", "*#_", "SHOW", "DELETE"];
/// Get a character at a specified index for a specified category.
fn get_char(current_category: &ChoiceCategory, index: u8) -> char {
@ -104,10 +107,10 @@ impl ChoiceFactoryPassphrase {
/// MENU choices with accept and cancel hold-to-confirm side buttons.
fn get_menu_item(&self, choice_index: u8) -> ChoiceItem {
let choice = MENU[choice_index as usize];
let item =
MultilineTextChoiceItem::new(String::from(choice), ButtonLayout::default_three_icons())
.use_delimiter(' ');
let mut menu_item = ChoiceItem::MultilineText(item);
let mut menu_item = ChoiceItem::new(
String::<50>::from(choice),
ButtonLayout::default_three_icons(),
);
// Including accept button on the left and cancel on the very right
// TODO: could have some icons instead of the shortcut text
@ -121,6 +124,13 @@ impl ChoiceFactoryPassphrase {
));
}
// Including icons for some items.
if choice_index == DELETE_INDEX as u8 {
menu_item = menu_item.with_icon(Icon::new(theme::ICON_DELETE));
} else if choice_index == SHOW_INDEX as u8 {
menu_item = menu_item.with_icon(Icon::new(theme::ICON_EYE));
}
menu_item
}
@ -128,13 +138,10 @@ impl ChoiceFactoryPassphrase {
/// return back
fn get_character_item(&self, choice_index: u8) -> ChoiceItem {
if is_menu_choice(&self.current_category, choice_index) {
let menu_choice =
TextChoiceItem::new("MENU", ButtonLayout::three_icons_middle_text("RETURN"));
ChoiceItem::Text(menu_choice)
ChoiceItem::new("MENU", ButtonLayout::three_icons_middle_text("RETURN"))
} else {
let ch = get_char(&self.current_category, choice_index);
let char_choice = BigCharacterChoiceItem::new(ch, ButtonLayout::default_three_icons());
ChoiceItem::BigCharacter(char_choice)
ChoiceItem::new(char_to_string::<1>(ch), ButtonLayout::default_three_icons())
}
}
}
@ -251,7 +258,7 @@ impl Component for PassphraseEntry {
match msg {
// Going to new category, applying some action or returning the result
Some(ChoicePageMsg::Choice(page_counter)) => match page_counter as usize {
DEL_INDEX => {
DELETE_INDEX => {
self.delete_last_digit(ctx);
self.update_passphrase_dots(ctx);
ctx.request_paint();
@ -324,7 +331,7 @@ impl crate::trace::Trace for PassphraseEntry {
let current_index = self.choice_page.page_index() as usize;
match &self.current_category {
ChoiceCategory::Menu => match current_index {
DEL_INDEX => ButtonAction::Action("Del last char").string(),
DELETE_INDEX => ButtonAction::Action("Del last char").string(),
SHOW_INDEX => ButtonAction::Action("Show pass").string(),
_ => ButtonAction::select_item(MENU[current_index]),
},

View File

@ -3,13 +3,15 @@ use crate::{
trezorhal::random,
ui::{
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
display::Icon,
geometry::Rect,
model_tr::theme,
},
};
use super::{
choice_item::BigCharacterChoiceItem, ButtonDetails, ButtonLayout, ChangingTextLine,
ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, MultilineTextChoiceItem,
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
ChoicePageMsg,
};
use heapless::String;
@ -22,16 +24,14 @@ const MAX_PIN_LENGTH: usize = 50;
const MAX_VISIBLE_DOTS: usize = 18;
const MAX_VISIBLE_DIGITS: usize = 18;
const CHOICE_LENGTH: usize = 14;
const EXIT_INDEX: usize = 0;
const DELETE_INDEX: usize = 1;
const SHOW_INDEX: usize = 2;
const PROMPT_INDEX: usize = 3;
const CHOICE_LENGTH: usize = 13;
const DELETE_INDEX: usize = 0;
const SHOW_INDEX: usize = 1;
const PROMPT_INDEX: usize = 2;
const CHOICES: [&str; CHOICE_LENGTH] = [
"EXIT",
"DELETE",
"SHOW PIN",
"PLACEHOLDER FOR THE PROMPT",
"SHOW",
"ENTER PIN",
"0",
"1",
"2",
@ -56,32 +56,23 @@ impl ChoiceFactoryPIN {
impl ChoiceFactory for ChoiceFactoryPIN {
fn get(&self, choice_index: u8) -> ChoiceItem {
let choice = CHOICES[choice_index as usize];
let choice_str = CHOICES[choice_index as usize];
// Depending on whether it is a digit (one character) or a text.
// Digits are BIG, the rest is multiline.
let mut choice_item = if choice.len() == 1 {
let item =
BigCharacterChoiceItem::from_str(choice, ButtonLayout::default_three_icons());
ChoiceItem::BigCharacter(item)
} else {
let item = MultilineTextChoiceItem::new(
String::from(choice),
ButtonLayout::default_three_icons(),
)
.use_delimiter(' ');
ChoiceItem::MultilineText(item)
};
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
// Action buttons have different middle button text
if [EXIT_INDEX, DELETE_INDEX, SHOW_INDEX, PROMPT_INDEX].contains(&(choice_index as usize)) {
if [DELETE_INDEX, SHOW_INDEX, PROMPT_INDEX].contains(&(choice_index as usize)) {
let confirm_btn = ButtonDetails::armed_text("CONFIRM");
choice_item.set_middle_btn(Some(confirm_btn));
}
// Changing the prompt text for the wanted one
if choice_index == PROMPT_INDEX as u8 {
choice_item.set_text(String::from(self.prompt.as_ref()));
// Adding icons for appropriate items
if choice_index == DELETE_INDEX as u8 {
choice_item = choice_item.with_icon(Icon::new(theme::ICON_DELETE));
} else if choice_index == SHOW_INDEX as u8 {
choice_item = choice_item.with_icon(Icon::new(theme::ICON_EYE));
} else if choice_index == PROMPT_INDEX as u8 {
choice_item = choice_item.with_icon(Icon::new(theme::ICON_TICK));
}
choice_item
@ -106,7 +97,7 @@ impl PinEntry {
Self {
choice_page: ChoicePage::new(choices)
.with_initial_page_counter(3)
.with_initial_page_counter(PROMPT_INDEX as u8)
.with_carousel(),
pin_dots: Child::new(ChangingTextLine::center_mono(String::new())),
show_real_pin: false,
@ -177,7 +168,6 @@ impl Component for PinEntry {
if let Some(ChoicePageMsg::Choice(page_counter)) = msg {
// Performing action under specific index or appending new digit
match page_counter as usize {
EXIT_INDEX => return Some(PinEntryMsg::Cancelled),
DELETE_INDEX => {
self.delete_last_digit(ctx);
self.update_pin_dots(ctx);
@ -194,8 +184,10 @@ impl Component for PinEntry {
self.append_new_digit(ctx, page_counter);
self.update_pin_dots(ctx);
// Choosing any random digit to be shown next
let new_page_counter =
random::uniform_between(4, (CHOICE_LENGTH - 1) as u32);
let new_page_counter = random::uniform_between(
PROMPT_INDEX as u32 + 1,
(CHOICE_LENGTH - 1) as u32,
);
self.choice_page
.set_page_counter(ctx, new_page_counter as u8);
ctx.request_paint();
@ -224,7 +216,6 @@ impl crate::trace::Trace for PinEntry {
ButtonPos::Middle => {
let current_index = self.choice_page.page_index() as usize;
match current_index {
EXIT_INDEX => ButtonAction::Cancel.string(),
DELETE_INDEX => ButtonAction::Action("Delete last digit").string(),
SHOW_INDEX => ButtonAction::Action("Show PIN").string(),
PROMPT_INDEX => ButtonAction::Confirm.string(),

View File

@ -3,7 +3,7 @@ use crate::ui::{
geometry::Rect,
};
use super::{ButtonLayout, ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, TextChoiceItem};
use super::{ButtonLayout, ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg};
use heapless::{String, Vec};
#[cfg(feature = "ui_debug")]
@ -32,8 +32,7 @@ where
{
fn get(&self, choice_index: u8) -> ChoiceItem {
let text = &self.choices[choice_index as usize];
let text_item = TextChoiceItem::new(text, ButtonLayout::default_three_icons());
let mut choice_item = ChoiceItem::Text(text_item);
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
// Disabling prev/next buttons for the first/last choice.
if choice_index == 0 {

View File

@ -1,7 +1,7 @@
use crate::ui::geometry::{Offset, Point, Rect};
pub const WIDTH: i16 = 128;
pub const HEIGHT: i16 = 128;
pub const HEIGHT: i16 = 64;
pub const LINE_SPACE: i16 = 1;
pub const FONT_BPP: i16 = 1;

View File

@ -21,9 +21,11 @@ use crate::{
text::paragraphs::{Paragraph, Paragraphs},
FormattedText,
},
display::Font,
layout::{
obj::{ComponentMsgObj, LayoutObj},
result::{CANCELLED, CONFIRMED, INFO}, util::iter_into_vec,
result::{CANCELLED, CONFIRMED, INFO},
util::iter_into_vec,
},
},
};
@ -160,9 +162,8 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
let obj = LayoutObj::new(Frame::new(
title,
None,
ButtonPage::new_str_buf(
FormattedText::new(theme::TEXT_NORMAL, theme::FORMATTED, format)
FormattedText::new(theme::TEXT_MONO, theme::FORMATTED, format)
.with("action", action.unwrap_or_default())
.with("description", description.unwrap_or_default()),
theme::BG,
@ -186,10 +187,9 @@ extern "C" fn new_confirm_text(n_args: usize, args: *const Obj, kwargs: *mut Map
let obj = LayoutObj::new(Frame::new(
title,
None,
ButtonPage::new_str(
Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, description.unwrap_or_default()),
Paragraph::new(&theme::TEXT_MONO, description.unwrap_or_default()),
Paragraph::new(&theme::TEXT_BOLD, data),
]),
theme::BG,
@ -222,7 +222,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
Some(ButtonDetails::text("CONTINUE")),
);
let btn_actions = ButtonActions::cancel_next();
Page::<20>::new(btn_layout, btn_actions).icon_label_text(
Page::<20>::new(btn_layout, btn_actions, Font::NORMAL).icon_label_text(
theme::ICON_USER,
"Recipient".into(),
address.clone(),
@ -239,7 +239,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
),
);
let btn_actions = ButtonActions::cancel_confirm();
Page::<20>::new(btn_layout, btn_actions)
Page::<20>::new(btn_layout, btn_actions, Font::NORMAL)
.icon_label_text(
theme::ICON_USER,
"Recipient".into(),
@ -281,7 +281,7 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
);
let btn_actions = ButtonActions::cancel_confirm();
let mut flow_page = Page::<25>::new(btn_layout, btn_actions)
let mut flow_page = Page::<25>::new(btn_layout, btn_actions, Font::NORMAL)
.icon_label_text(theme::ICON_PARAM, total_label.clone(), total_amount.clone())
.newline()
.icon_label_text(theme::ICON_PARAM, fee_label.clone(), fee_amount.clone());
@ -305,6 +305,8 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: u8 = 7;
let get_page = |page_index| {
// Lazy-loaded list of screens to show, with custom content,
// buttons and actions triggered by these buttons.
@ -314,29 +316,19 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
let screen = match page_index {
// title, text, btn_layout, btn_actions
0 => (
"Hello!",
"Welcome to Trezor.\n\n\nPress right to continue.",
"HELLO",
"Welcome to Trezor.\nPress right to continue.",
ButtonLayout::cancel_and_arrow(),
ButtonActions::last_next(),
),
1 => (
"Basics",
"Use Trezor by clicking left & right.\nPress right to continue.",
"",
"Use Trezor by clicking left & right.\n\nContinue right.",
ButtonLayout::left_right_arrows(),
ButtonActions::prev_next(),
),
2 => (
"Confirm",
"Press both left & right at the same time to confirm.",
ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()),
Some(ButtonDetails::armed_text("CONFIRM")),
None,
),
ButtonActions::prev_next_with_middle(),
),
3 => (
"Hold to confirm",
"HOLD TO CONFIRM",
"Press & hold right to approve important operations.",
ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()),
@ -348,29 +340,27 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
),
ButtonActions::prev_next(),
),
// TODO: merge these two scrolls into one, with using a scrollbar
4 => (
"Screen scroll",
"Press right to scroll down to read all content when text doesn't...",
3 => (
"SCREEN SCROLL",
"Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.",
ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()),
None,
Some(ButtonDetails::down_arrow_icon_wide()),
),
Some(ButtonDetails::text("GOT IT")), ),
ButtonActions::prev_next(),
),
4 => (
"CONFIRM",
"Press both left & right at the same time to confirm.",
ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()),
Some(ButtonDetails::armed_text("CONFIRM")),
None,
),
ButtonActions::prev_next_with_middle(),
),
5 => (
"Screen scroll",
"fit on one screen. Press left to scroll up.",
ButtonLayout::new(
Some(ButtonDetails::up_arrow_icon_wide()),
None,
Some(ButtonDetails::text("CONFIRM")),
),
ButtonActions::prev_next(),
),
6 => (
"Congrats!",
"CONGRATS!",
"You're ready to use Trezor.",
ButtonLayout::new(
Some(ButtonDetails::text("AGAIN")),
@ -379,27 +369,30 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
),
ButtonActions::beginning_confirm(),
),
7 => (
"Skip tutorial?",
6 => (
"SKIP TUTORIAL",
"Sure you want to skip the tutorial?",
ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()),
None,
Some(ButtonDetails::text("CONFIRM")),
Some(ButtonDetails::text("SKIP")),
),
ButtonActions::beginning_cancel(),
),
_ => unreachable!(),
};
Page::<10>::new(screen.2.clone(), screen.3.clone())
.text_bold(screen.0.into())
.newline()
.newline_half()
.text_normal(screen.1.into())
let mut page = Page::<10>::new(screen.2.clone(), screen.3.clone(), Font::BOLD);
// Add title if present
if !screen.0.is_empty() {
page = page.text_bold(screen.0.into()).newline().newline_half()
}
page = page.text_mono(screen.1.into());
page
};
let pages = FlowPages::new(get_page, 8);
let pages = FlowPages::new(get_page, PAGE_COUNT);
let obj = LayoutObj::new(Flow::new(pages).into_child())?;
Ok(obj.into())
@ -418,7 +411,7 @@ extern "C" fn pin_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
// as in the design.
0 => (
"PIN settings".into(),
"PIN should\ncontain at\nleast four\ndigits",
"PIN should contain at least 6 digits",
ButtonLayout::cancel_and_text("GOT IT"),
ButtonActions::cancel_next(),
),
@ -434,11 +427,11 @@ extern "C" fn pin_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
_ => unreachable!(),
};
Page::<10>::new(screen.2.clone(), screen.3.clone())
Page::<10>::new(screen.2.clone(), screen.3.clone(), Font::BOLD)
.text_bold(screen.0)
.newline()
.newline_half()
.text_normal(screen.1.into())
.text_mono(screen.1.into())
};
let pages = FlowPages::new(get_page, 2);
@ -464,7 +457,7 @@ extern "C" fn request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
let title = "Recovery seed";
let title = "RECOVERY SEED";
// Parsing the list of share words.
// Assume there is always up to 24 words in the newly generated seed
@ -519,13 +512,8 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
let obj = LayoutObj::new(Frame::new(
title,
None,
ButtonPage::new_str(
Paragraphs::new(
[
Paragraph::new(&theme::TEXT_BOLD, text_to_show)
]
),
Paragraphs::new([Paragraph::new(&theme::TEXT_BOLD, text_to_show)]),
theme::BG,
)
.with_cancel_btn(cancel_btn)
@ -539,15 +527,10 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
let words: Vec<StrBuffer, 3> = iter_into_vec(words_iterable)?;
let obj = LayoutObj::new(Frame::new(
title,
Some(description),
SimpleChoice::new(words).into_child(),
))?;
let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(words).into_child()))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -556,15 +539,10 @@ extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
extern "C" fn request_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let text: StrBuffer = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?;
let choices: Vec<&str, 5> = ["12", "18", "20", "24", "33"].into_iter().collect();
let obj = LayoutObj::new(Frame::new(
title,
Some(text),
SimpleChoice::new(choices).into_child(),
))?;
let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(choices).into_child()))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -574,7 +552,7 @@ extern "C" fn request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut M
let block = |_args: &[Obj], kwargs: &Map| {
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let obj = LayoutObj::new(Frame::new(prompt, None, Bip39Entry::new().into_child()))?;
let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new().into_child()))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -585,11 +563,7 @@ extern "C" fn request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut M
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let _max_len: u8 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
let obj = LayoutObj::new(Frame::new(
prompt,
None,
PassphraseEntry::new().into_child(),
))?;
let obj = LayoutObj::new(Frame::new(prompt, PassphraseEntry::new().into_child()))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -678,7 +652,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def select_word(
/// *,
/// title: str,
/// description: str,
/// words: Iterable[str],
/// ) -> int:
/// """Select mnemonic word from three possibilities - seed check after backup. The
@ -688,7 +661,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def request_word_count(
/// *,
/// title: str,
/// text: str,
/// ) -> str: # TODO: make it return int
/// """Get word count for recovery."""
Qstr::MP_QSTR_request_word_count => obj_fn_kw!(0, request_word_count).as_obj(),

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -9,7 +9,8 @@ pub const BG: Color = Color::black(); // Default background color.
// Font constants.
pub const FONT_BUTTON: Font = Font::MONO;
pub const FONT_HEADER: Font = Font::MONO;
pub const FONT_HEADER: Font = Font::BOLD;
pub const FONT_CHOICE_ITEMS: Font = Font::NORMAL;
// Text constants.
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, FG, BG, FG, FG);
@ -25,17 +26,6 @@ pub const FORMATTED: FormattedFonts = FormattedFonts {
};
// Icons with their names for debugging purposes
pub const ICON_SUCCESS: IconAndName =
IconAndName::new(include_res!("model_tr/res/success.toif"), "success");
pub const ICON_FAIL: IconAndName = IconAndName::new(include_res!("model_tr/res/fail.toif"), "fail");
pub const ICON_CANCEL_OUTLINE: IconAndName = IconAndName::new(
include_res!("model_tr/res/cancel_for_outline.toif"),
"cancel_outline",
); // 8*8
pub const ICON_CANCEL: IconAndName = IconAndName::new(
include_res!("model_tr/res/cancel_no_outline.toif"),
"cancel",
);
pub const ICON_ARM_LEFT: IconAndName =
IconAndName::new(include_res!("model_tr/res/arm_left.toif"), "arm_left"); // 6*10
pub const ICON_ARM_RIGHT: IconAndName =
@ -48,12 +38,27 @@ pub const ICON_ARROW_UP: IconAndName =
IconAndName::new(include_res!("model_tr/res/arrow_up.toif"), "arrow_up"); // 10*6
pub const ICON_ARROW_DOWN: IconAndName =
IconAndName::new(include_res!("model_tr/res/arrow_down.toif"), "arrow_down"); // 10*6
pub const ICON_BIN: IconAndName = IconAndName::new(include_res!("model_tr/res/bin.toif"), "bin"); // 10*10
pub const ICON_AMOUNT: IconAndName =
IconAndName::new(include_res!("model_tr/res/amount.toif"), "amount"); // 10*10
pub const ICON_BIN: IconAndName = IconAndName::new(include_res!("model_tr/res/bin.toif"), "bin"); // 10*10
pub const ICON_CANCEL_OUTLINE: IconAndName = IconAndName::new(
include_res!("model_tr/res/cancel_for_outline.toif"),
"cancel_outline",
); // 8*8
pub const ICON_CANCEL: IconAndName = IconAndName::new(
include_res!("model_tr/res/cancel_no_outline.toif"),
"cancel",
); // 8*8
pub const ICON_DELETE: IconAndName =
IconAndName::new(include_res!("model_tr/res/delete.toif"), "delete"); // 12*8
pub const ICON_EYE: IconAndName = IconAndName::new(include_res!("model_tr/res/eye.toif"), "eye"); // 12*6
pub const ICON_FAIL: IconAndName = IconAndName::new(include_res!("model_tr/res/fail.toif"), "fail");
pub const ICON_LOCK: IconAndName = IconAndName::new(include_res!("model_tr/res/lock.toif"), "lock"); // 10*10
pub const ICON_PARAM: IconAndName =
IconAndName::new(include_res!("model_tr/res/param.toif"), "param"); // 10*10
pub const ICON_SUCCESS: IconAndName =
IconAndName::new(include_res!("model_tr/res/success.toif"), "success");
pub const ICON_TICK: IconAndName = IconAndName::new(include_res!("model_tr/res/tick.toif"), "tick"); // 10*10
pub const ICON_USER: IconAndName = IconAndName::new(include_res!("model_tr/res/user.toif"), "user"); // 10*10
pub const ICON_WALLET: IconAndName =
IconAndName::new(include_res!("model_tr/res/wallet.toif"), "wallet"); // 10*10

View File

@ -109,7 +109,6 @@ def show_share_words(
def select_word(
*,
title: str,
description: str,
words: Iterable[str],
) -> int:
"""Select mnemonic word from three possibilities - seed check after backup. The
@ -120,7 +119,6 @@ def select_word(
def request_word_count(
*,
title: str,
text: str,
) -> str: # TODO: make it return int
"""Get word count for recovery."""

View File

@ -65,12 +65,10 @@ class HomescreenBase(ui.Layout):
return storage.device.get_homescreen() or res.load(
"apps/homescreen/res/bg.toif"
)
elif utils.MODEL in ("R",):
elif utils.MODEL in ("1", "R"):
# TODO: make it possible to change
# TODO: make it a requirement of 128x64 px
# TODO: make it a requirement of XxX px
# TODO: support it for ui.display.avatar, not only ui.display.icon
return res.load("trezor/res/model_r/homescreen.toif") # 128*64 px
elif utils.MODEL in ("1",):
return res.load("trezor/res/homescreen_model_1.toif") # 64x36 px
else:
raise Exception("Unknown model")

View File

@ -21,17 +21,17 @@ class Homescreen(HomescreenBase):
# the icon more on the top.
# Otherwise just showing the uppercase label in monospace.
if not storage.device.is_initialized():
ui.display.icon(0, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
ui.display.icon(32, 5, self.get_avatar(), ui.style.FG, ui.style.BG)
ui.display.text_center(
ui.WIDTH // 2, 98, "Go to", ui.BOLD, ui.FG, ui.BG
ui.WIDTH // 2, 52, "Go to", ui.BOLD, ui.FG, ui.BG
)
ui.display.text_center(
ui.WIDTH // 2, 112, "trezor.io/start", ui.BOLD, ui.FG, ui.BG
ui.WIDTH // 2, 60, "trezor.io/start", ui.BOLD, ui.FG, ui.BG
)
else:
ui.display.icon(0, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
ui.display.icon(32, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
ui.display.text_center(
ui.WIDTH // 2, 112, self.label.upper(), ui.MONO, ui.FG, ui.BG
ui.WIDTH // 2, 60, self.label.upper(), ui.MONO, ui.FG, ui.BG
)
@ -57,20 +57,20 @@ class Lockscreen(HomescreenBase):
ui.display.text_center(
ui.WIDTH // 2, 9, self.label.upper(), ui.MONO, ui.FG, ui.BG
)
ui.display.icon(0, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
ui.display.icon(32, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
# Lock icon placement depends on the lock_label text
lock_icon = ui.res.load("trezor/res/model_r/lock.toif")
if self.lock_label == "Not connected":
ui.display.icon(13, 90, lock_icon, ui.style.FG, ui.style.BG)
ui.display.icon(13, 45, lock_icon, ui.style.FG, ui.style.BG)
else:
ui.display.icon(38, 90, lock_icon, ui.style.FG, ui.style.BG)
ui.display.icon(38, 45, lock_icon, ui.style.FG, ui.style.BG)
ui.display.text_center(
ui.WIDTH // 2 + 10, 100, self.lock_label.upper(), ui.NORMAL, ui.FG, ui.BG
ui.WIDTH // 2 + 10, 52, self.lock_label.upper(), ui.NORMAL, ui.FG, ui.BG
)
ui.display.text_center(
ui.WIDTH // 2, 115, self.tap_label.upper(), ui.MONO, ui.FG, ui.BG
ui.WIDTH // 2, 60, self.tap_label.upper(), ui.MONO, ui.FG, ui.BG
)
def on_button_released(self, _x: int) -> None:

View File

@ -406,7 +406,7 @@ async def _placeholder_confirm(
confirm_text(
ctx=ctx,
br_type=br_type,
title=title,
title=title.upper(),
data=data,
description=description,
br_code=br_code,
@ -429,7 +429,7 @@ async def get_bool(
ctx,
RustLayout(
trezorui2.confirm_action(
title=title,
title=title.upper(),
action=data,
description=description,
verb=verb,
@ -501,7 +501,7 @@ async def confirm_action(
ctx,
RustLayout(
trezorui2.confirm_action(
title=title,
title=title.upper(),
action=action,
description=description,
verb=verb,
@ -565,7 +565,7 @@ async def confirm_reset_device(
return await _placeholder_confirm(
ctx=ctx,
br_type="recover_device" if recovery else "setup_device",
title="Recovery mode" if recovery else "Create new wallet",
title="RECOVERY MODE" if recovery else "CREATE NEW WALLET",
data="By continuing you agree to trezor.io/tos",
description=prompt,
br_code=ButtonRequestType.ProtectCall
@ -578,10 +578,10 @@ async def confirm_reset_device(
async def confirm_backup(ctx: wire.GenericContext) -> bool:
if await get_bool(
ctx=ctx,
title="Success",
data="\nNew wallet created successfully!\n\n\nYou should back up your new wallet right now.",
verb="Back up",
verb_cancel="Skip",
title="SUCCESS",
data="New wallet created successfully!\nYou should back up your new wallet right now.",
verb="BACK UP",
verb_cancel="SKIP",
br_type="backup_device",
br_code=ButtonRequestType.ResetDevice,
):
@ -589,11 +589,11 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool:
confirmed = await get_bool(
ctx=ctx,
title="Warning",
title="WARNING",
data="Are you sure you want to skip the backup?\n\n",
description="You can back up your Trezor once, at any time.",
verb="Back up",
verb_cancel="Skip",
verb="BACK UP",
verb_cancel="SKIP",
br_type="backup_device",
br_code=ButtonRequestType.ResetDevice,
)
@ -606,7 +606,7 @@ async def confirm_path_warning(
return await _placeholder_confirm(
ctx=ctx,
br_type="path_warning",
title="Confirm path",
title="CONFIRM PATH",
data=f"{path_type}\n{path} is unknown.\nAre you sure?",
description="",
br_code=ButtonRequestType.UnknownDerivationPath,
@ -619,7 +619,7 @@ async def show_xpub(
return await _placeholder_confirm(
ctx=ctx,
br_type="show_xpub",
title=title,
title=title.upper(),
data=xpub,
description="",
br_code=ButtonRequestType.PublicKey,
@ -648,7 +648,7 @@ async def show_address(
return await _placeholder_confirm(
ctx=ctx,
br_type="show_address",
title=title,
title=title.upper(),
data=text,
description="",
br_code=ButtonRequestType.Address,
@ -661,7 +661,7 @@ def show_pubkey(
return confirm_blob(
ctx,
br_type="show_pubkey",
title=title,
title=title.upper(),
data=pubkey,
br_code=ButtonRequestType.PublicKey,
)
@ -684,7 +684,7 @@ async def _show_modal(
ctx=ctx,
br_type=br_type,
br_code=br_code,
title=header,
title=header.upper(),
action=subheader,
description=content,
verb=button_confirm,
@ -832,7 +832,7 @@ async def confirm_payment_request(
return await _placeholder_confirm(
ctx=ctx,
br_type="confirm_payment_request",
title="Confirm sending",
title="CONFIRM SENDING",
data=f"{amount} to\n{recipient_name}\n{memos_str}",
description="",
br_code=ButtonRequestType.ConfirmOutput,
@ -853,7 +853,7 @@ async def should_show_more(
) -> bool:
return await get_bool(
ctx=ctx,
title=title,
title=title.upper(),
data=button_text,
br_type=br_type,
br_code=br_code,
@ -875,7 +875,7 @@ async def confirm_blob(
await _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=title,
title=title.upper(),
data=str(data),
description=description,
br_code=br_code,
@ -895,7 +895,7 @@ async def confirm_address(
return confirm_blob(
ctx=ctx,
br_type=br_type,
title=title,
title=title.upper(),
data=address,
description=description,
br_code=br_code,
@ -916,7 +916,7 @@ async def confirm_text(
ctx,
RustLayout(
trezorui2.confirm_text(
title=title,
title=title.upper(),
data=data,
description=description,
)
@ -940,7 +940,7 @@ def confirm_amount(
return _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=title,
title=title.upper(),
data=amount,
description=description,
br_code=br_code,
@ -960,7 +960,7 @@ async def confirm_properties(
await _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=title,
title=title.upper(),
data="\n\n".join(f"{name or ''}\n{value or ''}" for name, value in props),
description="",
br_code=br_code,
@ -984,7 +984,7 @@ async def confirm_total(
ctx,
RustLayout(
trezorui2.confirm_total_r(
title=title,
title=title.upper(),
total_amount=total_amount,
fee_amount=fee_amount,
fee_rate_amount=fee_rate_amount,
@ -1004,7 +1004,7 @@ async def confirm_joint_total(
await _placeholder_confirm(
ctx=ctx,
br_type="confirm_joint_total",
title="Joint transaction",
title="JOINT TRANSACTION",
data=f"You are contributing:\n{spending_amount}\nto the total amount:\n{total_amount}",
description="",
br_code=ButtonRequestType.SignTx,
@ -1032,7 +1032,7 @@ async def confirm_metadata(
await _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=title,
title=title.upper(),
data=text,
description="",
br_code=br_code,
@ -1045,7 +1045,7 @@ async def confirm_replacement(
await _placeholder_confirm(
ctx=ctx,
br_type="confirm_replacement",
title=description,
title=description.upper(),
data=f"Confirm transaction ID:\n{txid}",
description="",
br_code=ButtonRequestType.SignTx,
@ -1069,7 +1069,7 @@ async def confirm_modify_output(
await _placeholder_confirm(
ctx=ctx,
br_type="modify_output",
title="Modify amount",
title="MODIFY AMOUNT",
data=text,
description="",
br_code=ButtonRequestType.ConfirmOutput,
@ -1099,7 +1099,7 @@ async def confirm_modify_fee(
await _placeholder_confirm(
ctx=ctx,
br_type="modify_fee",
title="Modify fee",
title="MODIFY FEE",
data=text,
description="",
br_code=ButtonRequestType.SignTx,
@ -1112,7 +1112,7 @@ async def confirm_coinjoin(
await _placeholder_confirm(
ctx=ctx,
br_type="coinjoin_final",
title="Authorize CoinJoin",
title="AUTHORIZE COINJOIN",
data=f"Maximum rounds: {max_rounds}\n\nMaximum mining fee:\n{max_fee_per_vbyte} sats/vbyte",
description="",
br_code=ButtonRequestType.Other,
@ -1131,7 +1131,7 @@ async def confirm_sign_identity(
await _placeholder_confirm(
ctx=ctx,
br_type="confirm_sign_identity",
title=f"Sign {proto}",
title=f"Sign {proto}".upper(),
data=text,
description="",
br_code=ButtonRequestType.Other,
@ -1151,7 +1151,7 @@ async def confirm_signverify(
await _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=header,
title=header.upper(),
data=f"Confirm address:\n{address}",
description="",
br_code=ButtonRequestType.Other,
@ -1160,7 +1160,7 @@ async def confirm_signverify(
await _placeholder_confirm(
ctx=ctx,
br_type=br_type,
title=header,
title=header.upper(),
data=f"Confirm message:\n{message}",
description="",
br_code=ButtonRequestType.Other,

View File

@ -14,19 +14,11 @@ if TYPE_CHECKING:
async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
await button_request(ctx, "word_count", code=ButtonRequestType.MnemonicWordCount)
if dry_run:
title = "Seed check"
else:
title = "Recovery mode"
text = "Number of words?"
count = await interact(
ctx,
RustLayout(
trezorui2.request_word_count(
title=title,
text=text,
title="NUMBER OF WORDS",
)
),
br_type="request_word_count",
@ -39,7 +31,7 @@ async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
async def request_word(
ctx: wire.GenericContext, word_index: int, word_count: int, is_slip39: bool
) -> str:
prompt = f"Choose word {word_index + 1}/{word_count}"
prompt = f"WORD {word_index + 1} OF {word_count}"
if is_slip39:
raise NotImplementedError
@ -79,11 +71,13 @@ async def continue_recovery(
text: str,
subtext: str | None,
info_func: Callable | None,
dry_run: bool,
) -> bool:
return await get_bool(
ctx=ctx,
title="Recovery",
title="START RECOVERY",
data=f"{text}\n\n{subtext or ''}",
verb="START",
br_type="recovery",
br_code=ButtonRequestType.RecoveryHomepage,
)

View File

@ -39,19 +39,14 @@ async def select_word(
count: int,
group_index: int | None = None,
) -> str:
# TODO: it might not always be 3 words, it can happen it will be only two,
# but the probability is very small - 4 words containing two items two times
# (or one in "all all" seed)
assert len(words) == 3
if share_index is None:
title: str = "CHECK SEED"
elif group_index is None:
title = f"CHECK SHARE #{share_index + 1}"
else:
title = f"CHECK G{group_index + 1} - SHARE {share_index + 1}"
result = await ctx.wait(
RustLayout(
trezorui2.select_word(
title=title,
description=f"Select word {checked_index + 1}/{count}",
title=f"SELECT WORD {checked_index + 1}",
words=(words[0].upper(), words[1].upper(), words[2].upper()),
)
)
@ -91,19 +86,11 @@ async def slip39_advanced_prompt_group_threshold(
async def show_warning_backup(ctx: wire.GenericContext, slip39: bool) -> None:
if slip39:
description = (
"Never make a digital copy of your shares and never upload them online."
)
else:
description = (
"Never make a digital copy of your seed and never upload it online."
)
await confirm_action(
ctx,
"backup_warning",
"Caution",
description=description,
description="Never make a digital copy and never upload it online.",
verb="I understand",
verb_cancel=None,
br_code=ButtonRequestType.ResetDevice,

View File

@ -155,7 +155,6 @@ def process_face(
)
nonprintable += " };\n"
yMin = bearingY - rows
yMax = yMin + rows
font_ymin = min(font_ymin, yMin)
@ -178,7 +177,10 @@ def process_face(
f.write("#endif\n")
f.write("#define Font_%s_%s_%d_HEIGHT %d\n" % (name, style, size, size))
f.write("#define Font_%s_%s_%d_MAX_HEIGHT %d\n" % (name, style, size, font_ymax - font_ymin))
f.write(
"#define Font_%s_%s_%d_MAX_HEIGHT %d\n"
% (name, style, size, font_ymax - font_ymin)
)
f.write("#define Font_%s_%s_%d_BASELINE %d\n" % (name, style, size, -font_ymin))
f.write(
"extern const uint8_t* const Font_%s_%s_%d[%d + 1 - %d];\n"