1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +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 FROZEN = True
if TREZOR_MODEL in ('1', 'R'): if TREZOR_MODEL in ('1', 'R'):
FONT_NORMAL='Font_PixelOperator_Regular_8' FONT_NORMAL='Font_Unifont_Regular_16'
FONT_DEMIBOLD=None FONT_DEMIBOLD='Font_Unifont_Bold_16'
FONT_BOLD='Font_PixelOperator_Bold_8' FONT_BOLD='Font_PixelOperator_Bold_8'
FONT_MONO='Font_PixelOperatorMono_Regular_8' FONT_MONO='Font_PixelOperatorMono_Regular_8'
if TREZOR_MODEL in ('T', ): 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' RASPI = os.getenv('TREZOR_EMULATOR_RASPI') == '1'
if TREZOR_MODEL in ('1', 'R'): if TREZOR_MODEL in ('1', 'R'):
FONT_NORMAL='Font_PixelOperator_Regular_8' FONT_NORMAL='Font_Unifont_Regular_16'
FONT_DEMIBOLD='Font_PixelOperator_Regular_8' FONT_DEMIBOLD='Font_Unifont_Bold_16'
FONT_BOLD='Font_PixelOperator_Bold_8' FONT_BOLD='Font_PixelOperator_Bold_8'
FONT_MONO='Font_PixelOperatorMono_Regular_8' FONT_MONO='Font_PixelOperatorMono_Regular_8'
if TREZOR_MODEL in ('T', ): 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" #include "background_T.h"
BACKGROUND = IMG_LoadTexture_RW( BACKGROUND = IMG_LoadTexture_RW(
RENDERER, SDL_RWFromMem(background_T_jpg, background_T_jpg_len), 0); 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" #include "background_1.h"
BACKGROUND = IMG_LoadTexture_RW( BACKGROUND = IMG_LoadTexture_RW(
RENDERER, SDL_RWFromMem(background_1_jpg, background_1_jpg_len), 0); RENDERER, SDL_RWFromMem(background_1_jpg, background_1_jpg_len), 0);

View File

@ -29,7 +29,7 @@
#define DISPLAY_RESY 240 #define DISPLAY_RESY 240
#define TREZOR_FONT_BPP 4 #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_RESX 128
#define MAX_DISPLAY_RESY 64 #define MAX_DISPLAY_RESY 64
@ -37,14 +37,6 @@
#define DISPLAY_RESY 64 #define DISPLAY_RESY 64
#define TREZOR_FONT_BPP 1 #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 #else
#error Unknown Trezor model #error Unknown Trezor model
#endif #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 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 // - 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_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_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 }; /* # */ 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 #if TREZOR_FONT_BPP != 1
#error Wrong TREZOR_FONT_BPP (expected 1) #error Wrong TREZOR_FONT_BPP (expected 1)
#endif #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* const Font_Unifont_Bold_16[126 + 1 - 32];
extern const uint8_t Font_Unifont_Bold_16_glyph_nonprintable[]; extern const uint8_t Font_Unifont_Bold_16_glyph_nonprintable[];

View File

@ -3,5 +3,8 @@
#if TREZOR_FONT_BPP != 1 #if TREZOR_FONT_BPP != 1
#error Wrong TREZOR_FONT_BPP (expected 1) #error Wrong TREZOR_FONT_BPP (expected 1)
#endif #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* const Font_Unifont_Regular_16[126 + 1 - 32];
extern const uint8_t Font_Unifont_Regular_16_glyph_nonprintable[]; 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"))] #[cfg(any(feature = "model_tt", feature = "model_tr"))]
pub mod loader; pub mod loader;
pub mod icon;
pub mod font;
pub mod color;
use super::{ use super::{
constant, constant,
@ -29,11 +29,11 @@ use crate::{
}; };
// Reexports // Reexports
pub use color::Color;
pub use font::{Font, Glyph};
pub use icon::{Icon, IconAndName};
#[cfg(any(feature = "model_tt", feature = "model_tr"))] #[cfg(any(feature = "model_tt", feature = "model_tr"))]
pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN}; 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 { pub fn backlight() -> i32 {
display::backlight(-1) 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); 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) { pub fn icon_rect(r: Rect, toif_data: &[u8], fg_color: Color, bg_color: Color) {
display::icon( display::icon(
r.x0, r.x0,
@ -211,6 +212,7 @@ pub fn rect_outline_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u
if radius == 1 { if radius == 1 {
rect_fill_rounded(r, fg_color, bg_color, 1); rect_fill_rounded(r, fg_color, bg_color, 1);
rect_fill(inner_r, bg_color); rect_fill(inner_r, bg_color);
rect_fill_corners(inner_r, fg_color);
} else if radius == 2 { } else if radius == 2 {
rect_fill_rounded(r, fg_color, bg_color, 2); rect_fill_rounded(r, fg_color, bg_color, 2);
rect_fill_rounded(inner_r, bg_color, fg_color, 1); 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()) 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 { pub const fn left_center(&self) -> Point {
self.bottom_left().center(self.top_left()) self.bottom_left().center(self.top_left())
} }

View File

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

View File

@ -233,10 +233,18 @@ where
text_color, text_color,
background_color, background_color,
); );
display::rect_fill_corners(area_to_fill, theme::BG);
} else if style.with_outline { } else if style.with_outline {
display::rect_outline_rounded(area, text_color, background_color, 2); if background_color == theme::BG {
display::rect_outline_rounded(area, text_color, background_color, 2);
} else {
// 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 { } else {
display::rect_fill(area, background_color) display::rect_fill(area, background_color);
} }
match &self.content { match &self.content {

View File

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

View File

@ -3,7 +3,7 @@ use crate::ui::{
geometry::Rect, geometry::Rect,
}; };
use super::{theme, ButtonController, ButtonControllerMsg, ButtonPos, ChoiceItem, ChoiceItemAPI}; use super::{theme, ButtonController, ButtonControllerMsg, ButtonPos, ChoiceItem};
pub enum ChoicePageMsg { pub enum ChoicePageMsg {
Choice(u8), Choice(u8),
@ -11,7 +11,8 @@ pub enum ChoicePageMsg {
RightMost, 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 /// Interface for a specific component efficiently giving
/// `ChoicePage` all the information it needs to render /// `ChoicePage` all the information it needs to render
@ -48,7 +49,18 @@ where
pad: Pad, pad: Pad,
buttons: Child<ButtonController<&'static str>>, buttons: Child<ButtonController<&'static str>>,
page_counter: u8, 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, 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> impl<F> ChoicePage<F>
@ -63,7 +75,11 @@ where
pad: Pad::with_background(theme::BG), pad: Pad::with_background(theme::BG),
buttons: Child::new(ButtonController::new(initial_btn_layout)), buttons: Child::new(ButtonController::new(initial_btn_layout)),
page_counter: 0, page_counter: 0,
y_baseline: DEFAULT_Y_BASELINE,
items_distance: DEFAULT_ITEMS_DISTANCE,
is_carousel: false, is_carousel: false,
show_incomplete: false,
inverse_selected_item: false,
} }
} }
@ -79,9 +95,30 @@ where
self 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 /// Resetting the component, which enables reusing the same instance
/// for multiple choice categories. /// 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 /// 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 /// 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 /// properly clean up the previous instance - it would still be shown on
@ -97,8 +134,8 @@ where
if reset_page_counter { if reset_page_counter {
self.page_counter = 0; self.page_counter = 0;
} }
self.update(ctx);
self.is_carousel = is_carousel; self.is_carousel = is_carousel;
self.update(ctx);
} }
/// Navigating to the chosen page index. /// Navigating to the chosen page index.
@ -107,24 +144,26 @@ where
self.update(ctx); self.update(ctx);
} }
/// Display current, previous and next choice according to /// Display current, previous and next choices according to
/// the current ChoiceItem. /// the current ChoiceItem.
fn paint_choices(&mut self) { fn paint_choices(&mut self) {
// Performing the appropriate `paint_XXX()` for the main choice let available_area = self.pad.area.split_top(self.y_baseline).0;
// and two adjacent choices when present
// In case of carousel mode, also showing the ones from other end.
self.show_current_choice();
if self.has_previous_choice() { // Drawing the current item in the middle.
self.show_previous_choice(); self.show_current_choice(available_area);
} else if self.is_carousel {
self.show_last_choice_on_left(); // 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() { // Possibly drawing on the right side.
self.show_next_choice(); if self.has_next_choice() || self.is_carousel {
} else if self.is_carousel { self.show_right_choices(right_area);
self.show_first_choice_on_right();
} }
} }
@ -140,62 +179,139 @@ where
ctx.request_paint(); ctx.request_paint();
} }
/// Index of the last page.
fn last_page_index(&self) -> u8 { fn last_page_index(&self) -> u8 {
self.choices.count() as u8 - 1 self.choices.count() as u8 - 1
} }
/// Whether there is a previous choice (on the left).
pub fn has_previous_choice(&self) -> bool { pub fn has_previous_choice(&self) -> bool {
self.page_counter > 0 self.page_counter > 0
} }
/// Whether there is a next choice (on the right).
pub fn has_next_choice(&self) -> bool { pub fn has_next_choice(&self) -> bool {
self.page_counter < self.last_page_index() self.page_counter < self.last_page_index()
} }
/// Gets choice at the current page index.
fn current_choice(&self) -> ChoiceItem { fn current_choice(&self) -> ChoiceItem {
self.get_choice(self.page_counter) self.get_choice(self.page_counter)
} }
/// Gets choice at the given page index.
fn get_choice(&self, index: u8) -> ChoiceItem { fn get_choice(&self, index: u8) -> ChoiceItem {
self.choices.get(index) self.choices.get(index)
} }
fn show_current_choice(&self) { /// Display the current choice in the middle.
self.current_choice().paint_center(); 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) { /// Display all the choices fitting on the left side.
self.get_choice(self.page_counter - 1).paint_left(); /// 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;
}
}
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;
}
// 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_next_choice(&self) { /// Display all the choices fitting on the right side.
self.get_choice(self.page_counter + 1).paint_right(); /// Going as far as possible.
} fn show_right_choices(&self, area: Rect) {
let mut page_index = self.page_counter + 1;
fn show_last_choice_on_left(&self) { let mut x_offset = 3; // starts with a little offset to account for the middle highlight
self.get_choice(self.last_page_index()).paint_left(); loop {
} // Breaking out of the loop if we exhausted right items
// and the carousel mode is not enabled.
fn show_first_choice_on_right(&self) { if page_index > self.last_page_index() {
self.get_choice(0).paint_right(); 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) { fn decrease_page_counter(&mut self) {
self.page_counter -= 1; self.page_counter -= 1;
} }
/// Advance page counter to the next page.
fn increase_page_counter(&mut self) { fn increase_page_counter(&mut self) {
self.page_counter += 1; self.page_counter += 1;
} }
/// Set page to the first one.
fn page_counter_to_zero(&mut self) { fn page_counter_to_zero(&mut self) {
self.page_counter = 0; self.page_counter = 0;
} }
/// Set page to the last one.
fn page_counter_to_max(&mut self) { fn page_counter_to_max(&mut self) {
self.page_counter = self.last_page_index(); self.page_counter = self.last_page_index();
} }
/// Get current page counter.
pub fn page_index(&self) -> u8 { pub fn page_index(&self) -> u8 {
self.page_counter self.page_counter
} }
@ -238,6 +354,7 @@ where
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let button_event = self.buttons.event(ctx, event); let button_event = self.buttons.event(ctx, event);
// Button was "triggered" - released. Doing the appropriate action.
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event { if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
match pos { match pos {
ButtonPos::Left => { 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 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 heapless::String;
use super::{ use super::{
common::{display, display_center, display_right}, common::{display, display_inverse, display_right},
ButtonDetails, ButtonLayout, ButtonDetails, ButtonLayout,
}; };
const MIDDLE_ROW: i16 = 61; /// Simple string component used as a choice item.
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
#[derive(Clone)] #[derive(Clone)]
pub enum ChoiceItem { pub struct ChoiceItem {
Text(TextChoiceItem), text: String<50>,
MultilineText(MultilineTextChoiceItem), icon: Option<Icon>,
BigCharacter(BigCharacterChoiceItem), btn_layout: ButtonLayout<&'static str>,
font: Font,
} }
impl ChoiceItem { 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 pub fn new<T>(text: T, btn_layout: ButtonLayout<&'static str>) -> Self
where where
T: AsRef<str>, T: AsRef<str>,
{ {
Self { Self {
text: String::from(text.as_ref()), text: String::from(text.as_ref()),
icon: None,
btn_layout, btn_layout,
} font: theme::FONT_CHOICE_ITEMS,
}
}
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,
} }
} }
/// Allows for changing the line delimiter to arbitrary char. /// Allows to add the icon.
pub fn use_delimiter(mut self, delimiter: char) -> Self { pub fn with_icon(mut self, icon: Icon) -> Self {
self.delimiter = delimiter; self.icon = Some(icon);
self self
} }
}
// TODO: Make all the text be centered vertically - account for amount of lines. /// Allows to change the font.
impl ChoiceItemAPI for MultilineTextChoiceItem { pub fn with_font(mut self, font: Font) -> Self {
fn paint_center(&mut self) { self.font = font;
// Displaying the center choice lower than the rest, self
// 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(); /// Getting the text width in pixels.
display_center(Point::new(MIDDLE_COL, offset), &line, Font::NORMAL); 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) { /// Whether the whole item fits into the given rectangle.
for (index, line) in self.text.split(self.delimiter).enumerate() { pub fn fits(&self, rect: Rect) -> bool {
let offset = MIDDLE_ROW + index as i16 * row_height(); self.width_side() <= rect.width()
display(Point::new(LEFT_COL, offset), &line, Font::NORMAL);
}
} }
fn paint_right(&mut self) { /// Draws highlight around this choice item.
for (index, line) in self.text.split(self.delimiter).enumerate() { /// Must be called before the item is drawn, otherwise it will
let offset = MIDDLE_ROW + index as i16 * row_height(); /// cover the item.
display_right(Point::new(RIGHT_COL, offset), &line, Font::NORMAL); 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);
fn btn_layout(&self) -> ButtonLayout<&'static str> { let outline_size = Offset::new(
self.btn_layout.clone() self.width_center() + 2 * bound,
} self.font.text_height() + 2 * bound - 3, // -3 because font is actually smaller
}
/// 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,
); );
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 { let mut baseline = area.bottom_center() + Offset::new(-self.width_center() / 2, 0);
fn paint_center(&mut self) { if let Some(icon) = self.icon {
self._paint_char(Point::new(MIDDLE_COL - 12, MIDDLE_ROW + 9)); 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) { /// Painting the item as a choice on the left side from center.
self._paint_char(Point::new(LEFT_COL, MIDDLE_ROW)); /// 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) { /// Painting the item as a choice on the right side from center.
self._paint_char(Point::new(RIGHT_COL - 12, MIDDLE_ROW)); /// 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() 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")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ChoiceItem { impl crate::trace::Trace for ChoiceItem {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ChoiceItem"); 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.content_flag();
t.string(&self.text); t.string(&self.text);
t.content_flag(); t.content_flag();
t.close(); 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::{ use crate::ui::{
display::{self, Font, Icon}, display::{self, Font, Icon},
geometry::{Offset, Point}, geometry::{Offset, Point, Rect},
model_tr::constant,
}; };
use heapless::String; use heapless::String;
use super::theme; use super::theme;
/// Display header text. /// Display white text on black background
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
pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) { pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
display::text(baseline, text.as_ref(), font, theme::FG, theme::BG); 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, /// Display white text on black background,
/// centered around a baseline Point /// centered around a baseline Point
pub fn display_center<T: AsRef<str>>(baseline: Point, text: &T, font: Font) { 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 /// Display title/header centered at the top of the given area.
/// the entire width.
/// Returning the painted height of the whole header. /// 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 text_heigth = theme::FONT_HEADER.text_height();
let title_baseline = top_left + Offset::y(text_heigth); let title_baseline = area.top_center() + Offset::y(text_heigth);
display_header(title_baseline, title); display::text_center(
// Optionally painting the subtitle as well title_baseline,
// (and offsetting the dotted line in that case) title.as_ref(),
let mut dotted_line_offset = text_heigth + 2; theme::FONT_HEADER,
if let Some(subtitle) = subtitle { theme::FG,
dotted_line_offset += text_heigth; theme::BG,
display_header(title_baseline + Offset::y(text_heigth), subtitle); );
} text_heigth
let line_start = top_left + Offset::y(dotted_line_offset);
display::dotted_line_horizontal(line_start, constant::WIDTH, theme::FG, 2);
dotted_line_offset
} }
/// Draws icon and text on the same line - icon on the left. /// Draws icon and text on the same line - icon on the left.

View File

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

View File

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

View File

@ -1,11 +1,6 @@
//! Mostly copy-pasted stuff from ui/component/text, //! Mostly copy-pasted stuff from ui/component/text,
//! but with small modifications. //! but with small modifications.
//! It is really mostly changing Op::Text(&'a str) to Op::Text(String<100>), //! (support for more Ops like icon drawing or arbitrary offsets)
//! 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).
use crate::{ use crate::{
micropython::buffer::StrBuffer, 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)] #[derive(Clone)]
pub enum Op { pub enum Op {
/// Render text with current color and font. /// 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. /// Baseline `Point` where we are starting to draw the text.
pub fn initial_cursor(&self) -> Point { pub fn initial_cursor(&self) -> Point {
self.bounds.top_left() + Offset::y(self.style.text_font.text_height() + self.padding_top) 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 /// Y coordinate of the bottom of the available space/bounds
pub fn bottom_y(&self) -> i16 { pub fn bottom_y(&self) -> i16 {
(self.bounds.y1 - self.padding_bottom).max(self.bounds.y0) (self.bounds.y1 - self.padding_bottom).max(self.bounds.y0)
@ -433,7 +413,6 @@ impl TextLayout {
cursor.x += icon.width() as i16; cursor.x += icon.width() as i16;
LayoutFit::Fitting { LayoutFit::Fitting {
// TODO: how to handle this? It could collide with "skip_first_n_bytes"
processed_chars: 1, processed_chars: 1,
height: 0, // it should just draw on one line 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. /// Visitor for text segment operations.
/// Defines responses for certain kind of events encountered /// Defines responses for certain kind of events encountered
/// when processing the content. /// when processing the content.

View File

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

View File

@ -32,7 +32,7 @@ pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
pub use button_controller::{ButtonController, ButtonControllerMsg}; pub use button_controller::{ButtonController, ButtonControllerMsg};
pub use changing_text::ChangingTextLine; pub use changing_text::ChangingTextLine;
pub use choice::{ChoiceFactory, ChoicePage, ChoicePageMsg}; 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 dialog::{Dialog, DialogMsg};
pub use flow::{Flow, FlowMsg}; pub use flow::{Flow, FlowMsg};
pub use flow_pages::{FlowPages, Page}; pub use flow_pages::{FlowPages, Page};

View File

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

View File

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

View File

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

View File

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

View File

@ -21,9 +21,11 @@ use crate::{
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
FormattedText, FormattedText,
}, },
display::Font,
layout::{ layout::{
obj::{ComponentMsgObj, LayoutObj}, 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( let obj = LayoutObj::new(Frame::new(
title, title,
None,
ButtonPage::new_str_buf( 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("action", action.unwrap_or_default())
.with("description", description.unwrap_or_default()), .with("description", description.unwrap_or_default()),
theme::BG, 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( let obj = LayoutObj::new(Frame::new(
title, title,
None,
ButtonPage::new_str( ButtonPage::new_str(
Paragraphs::new([ 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), Paragraph::new(&theme::TEXT_BOLD, data),
]), ]),
theme::BG, theme::BG,
@ -222,7 +222,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
Some(ButtonDetails::text("CONTINUE")), Some(ButtonDetails::text("CONTINUE")),
); );
let btn_actions = ButtonActions::cancel_next(); 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, theme::ICON_USER,
"Recipient".into(), "Recipient".into(),
address.clone(), 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(); 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( .icon_label_text(
theme::ICON_USER, theme::ICON_USER,
"Recipient".into(), "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 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()) .icon_label_text(theme::ICON_PARAM, total_label.clone(), total_amount.clone())
.newline() .newline()
.icon_label_text(theme::ICON_PARAM, fee_label.clone(), fee_amount.clone()); .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 { extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| { let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: u8 = 7;
let get_page = |page_index| { let get_page = |page_index| {
// Lazy-loaded list of screens to show, with custom content, // Lazy-loaded list of screens to show, with custom content,
// buttons and actions triggered by these buttons. // 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 { let screen = match page_index {
// title, text, btn_layout, btn_actions // title, text, btn_layout, btn_actions
0 => ( 0 => (
"Hello!", "HELLO",
"Welcome to Trezor.\n\n\nPress right to continue.", "Welcome to Trezor.\nPress right to continue.",
ButtonLayout::cancel_and_arrow(), ButtonLayout::cancel_and_arrow(),
ButtonActions::last_next(), ButtonActions::last_next(),
), ),
1 => ( 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(), ButtonLayout::left_right_arrows(),
ButtonActions::prev_next(), ButtonActions::prev_next(),
), ),
2 => ( 2 => (
"Confirm", "HOLD TO 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",
"Press & hold right to approve important operations.", "Press & hold right to approve important operations.",
ButtonLayout::new( ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()), 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(), ButtonActions::prev_next(),
), ),
// TODO: merge these two scrolls into one, with using a scrollbar 3 => (
4 => ( "SCREEN SCROLL",
"Screen scroll", "Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.",
"Press right to scroll down to read all content when text doesn't...",
ButtonLayout::new( ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()), Some(ButtonDetails::left_arrow_icon()),
None, None,
Some(ButtonDetails::down_arrow_icon_wide()), Some(ButtonDetails::text("GOT IT")), ),
),
ButtonActions::prev_next(), 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 => ( 5 => (
"Screen scroll", "CONGRATS!",
"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!",
"You're ready to use Trezor.", "You're ready to use Trezor.",
ButtonLayout::new( ButtonLayout::new(
Some(ButtonDetails::text("AGAIN")), 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(), ButtonActions::beginning_confirm(),
), ),
7 => ( 6 => (
"Skip tutorial?", "SKIP TUTORIAL",
"Sure you want to skip the tutorial?", "Sure you want to skip the tutorial?",
ButtonLayout::new( ButtonLayout::new(
Some(ButtonDetails::left_arrow_icon()), Some(ButtonDetails::left_arrow_icon()),
None, None,
Some(ButtonDetails::text("CONFIRM")), Some(ButtonDetails::text("SKIP")),
), ),
ButtonActions::beginning_cancel(), ButtonActions::beginning_cancel(),
), ),
_ => unreachable!(), _ => unreachable!(),
}; };
Page::<10>::new(screen.2.clone(), screen.3.clone()) let mut page = Page::<10>::new(screen.2.clone(), screen.3.clone(), Font::BOLD);
.text_bold(screen.0.into())
.newline() // Add title if present
.newline_half() if !screen.0.is_empty() {
.text_normal(screen.1.into()) 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())?; let obj = LayoutObj::new(Flow::new(pages).into_child())?;
Ok(obj.into()) 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. // as in the design.
0 => ( 0 => (
"PIN settings".into(), "PIN settings".into(),
"PIN should\ncontain at\nleast four\ndigits", "PIN should contain at least 6 digits",
ButtonLayout::cancel_and_text("GOT IT"), ButtonLayout::cancel_and_text("GOT IT"),
ButtonActions::cancel_next(), ButtonActions::cancel_next(),
), ),
@ -434,11 +427,11 @@ extern "C" fn pin_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
_ => unreachable!(), _ => 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) .text_bold(screen.0)
.newline() .newline()
.newline_half() .newline_half()
.text_normal(screen.1.into()) .text_mono(screen.1.into())
}; };
let pages = FlowPages::new(get_page, 2); 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 { extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| { let block = |_args: &[Obj], kwargs: &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 title = "Recovery seed"; let title = "RECOVERY SEED";
// Parsing the list of share words. // Parsing the list of share words.
// Assume there is always up to 24 words in the newly generated seed // 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( let obj = LayoutObj::new(Frame::new(
title, title,
None,
ButtonPage::new_str( ButtonPage::new_str(
Paragraphs::new( Paragraphs::new([Paragraph::new(&theme::TEXT_BOLD, text_to_show)]),
[
Paragraph::new(&theme::TEXT_BOLD, text_to_show)
]
),
theme::BG, theme::BG,
) )
.with_cancel_btn(cancel_btn) .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 { extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| { let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; 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_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
let words: Vec<StrBuffer, 3> = iter_into_vec(words_iterable)?; let words: Vec<StrBuffer, 3> = iter_into_vec(words_iterable)?;
let obj = LayoutObj::new(Frame::new( let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(words).into_child()))?;
title,
Some(description),
SimpleChoice::new(words).into_child(),
))?;
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) }
@ -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 { extern "C" fn request_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| { let block = |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; 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 choices: Vec<&str, 5> = ["12", "18", "20", "24", "33"].into_iter().collect();
let obj = LayoutObj::new(Frame::new( let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(choices).into_child()))?;
title,
Some(text),
SimpleChoice::new(choices).into_child(),
))?;
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) }
@ -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 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, None, Bip39Entry::new().into_child()))?; let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new().into_child()))?;
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) }
@ -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 prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let _max_len: u8 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?; let _max_len: u8 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
let obj = LayoutObj::new(Frame::new( let obj = LayoutObj::new(Frame::new(prompt, PassphraseEntry::new().into_child()))?;
prompt,
None,
PassphraseEntry::new().into_child(),
))?;
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) }
@ -678,7 +652,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
/// def select_word( /// def select_word(
/// *, /// *,
/// title: str, /// title: str,
/// description: str,
/// words: Iterable[str], /// words: Iterable[str],
/// ) -> int: /// ) -> int:
/// """Select mnemonic word from three possibilities - seed check after backup. The /// """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( /// def request_word_count(
/// *, /// *,
/// title: str, /// title: str,
/// text: str,
/// ) -> str: # TODO: make it return int /// ) -> str: # TODO: make it return int
/// """Get word count for recovery.""" /// """Get word count for recovery."""
Qstr::MP_QSTR_request_word_count => obj_fn_kw!(0, request_word_count).as_obj(), 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. // Font constants.
pub const FONT_BUTTON: Font = Font::MONO; 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. // Text constants.
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, FG, BG, FG, FG); 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 // 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 = pub const ICON_ARM_LEFT: IconAndName =
IconAndName::new(include_res!("model_tr/res/arm_left.toif"), "arm_left"); // 6*10 IconAndName::new(include_res!("model_tr/res/arm_left.toif"), "arm_left"); // 6*10
pub const ICON_ARM_RIGHT: IconAndName = 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 IconAndName::new(include_res!("model_tr/res/arrow_up.toif"), "arrow_up"); // 10*6
pub const ICON_ARROW_DOWN: IconAndName = pub const ICON_ARROW_DOWN: IconAndName =
IconAndName::new(include_res!("model_tr/res/arrow_down.toif"), "arrow_down"); // 10*6 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 = pub const ICON_AMOUNT: IconAndName =
IconAndName::new(include_res!("model_tr/res/amount.toif"), "amount"); // 10*10 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_LOCK: IconAndName = IconAndName::new(include_res!("model_tr/res/lock.toif"), "lock"); // 10*10
pub const ICON_PARAM: IconAndName = pub const ICON_PARAM: IconAndName =
IconAndName::new(include_res!("model_tr/res/param.toif"), "param"); // 10*10 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_USER: IconAndName = IconAndName::new(include_res!("model_tr/res/user.toif"), "user"); // 10*10
pub const ICON_WALLET: IconAndName = pub const ICON_WALLET: IconAndName =
IconAndName::new(include_res!("model_tr/res/wallet.toif"), "wallet"); // 10*10 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( def select_word(
*, *,
title: str, title: str,
description: str,
words: Iterable[str], words: Iterable[str],
) -> int: ) -> int:
"""Select mnemonic word from three possibilities - seed check after backup. The """Select mnemonic word from three possibilities - seed check after backup. The
@ -120,7 +119,6 @@ def select_word(
def request_word_count( def request_word_count(
*, *,
title: str, title: str,
text: str,
) -> str: # TODO: make it return int ) -> str: # TODO: make it return int
"""Get word count for recovery.""" """Get word count for recovery."""

View File

@ -65,12 +65,10 @@ class HomescreenBase(ui.Layout):
return storage.device.get_homescreen() or res.load( return storage.device.get_homescreen() or res.load(
"apps/homescreen/res/bg.toif" "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 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 # 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 return res.load("trezor/res/homescreen_model_1.toif") # 64x36 px
else: else:
raise Exception("Unknown model") raise Exception("Unknown model")

View File

@ -21,17 +21,17 @@ class Homescreen(HomescreenBase):
# the icon more on the top. # the icon more on the top.
# Otherwise just showing the uppercase label in monospace. # Otherwise just showing the uppercase label in monospace.
if not storage.device.is_initialized(): 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.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.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: 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.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.display.text_center(
ui.WIDTH // 2, 9, self.label.upper(), ui.MONO, ui.FG, ui.BG 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 placement depends on the lock_label text
lock_icon = ui.res.load("trezor/res/model_r/lock.toif") lock_icon = ui.res.load("trezor/res/model_r/lock.toif")
if self.lock_label == "Not connected": 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: 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.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.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: def on_button_released(self, _x: int) -> None:

View File

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

View File

@ -39,19 +39,14 @@ async def select_word(
count: int, count: int,
group_index: int | None = None, group_index: int | None = None,
) -> str: ) -> 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 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( result = await ctx.wait(
RustLayout( RustLayout(
trezorui2.select_word( trezorui2.select_word(
title=title, title=f"SELECT WORD {checked_index + 1}",
description=f"Select word {checked_index + 1}/{count}",
words=(words[0].upper(), words[1].upper(), words[2].upper()), 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: 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( await confirm_action(
ctx, ctx,
"backup_warning", "backup_warning",
"Caution", "Caution",
description=description, description="Never make a digital copy and never upload it online.",
verb="I understand", verb="I understand",
verb_cancel=None, verb_cancel=None,
br_code=ButtonRequestType.ResetDevice, br_code=ButtonRequestType.ResetDevice,

View File

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