mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-18 13:38:12 +00:00
WIP - designs for smaller screen
This commit is contained in:
parent
e9f4d91118
commit
b4c283230e
@ -25,8 +25,8 @@ PYOPT = ARGUMENTS.get('PYOPT', '1')
|
||||
FROZEN = True
|
||||
|
||||
if TREZOR_MODEL in ('1', 'R'):
|
||||
FONT_NORMAL='Font_PixelOperator_Regular_8'
|
||||
FONT_DEMIBOLD=None
|
||||
FONT_NORMAL='Font_Unifont_Regular_16'
|
||||
FONT_DEMIBOLD='Font_Unifont_Bold_16'
|
||||
FONT_BOLD='Font_PixelOperator_Bold_8'
|
||||
FONT_MONO='Font_PixelOperatorMono_Regular_8'
|
||||
if TREZOR_MODEL in ('T', ):
|
||||
|
@ -26,8 +26,8 @@ FROZEN = ARGUMENTS.get('TREZOR_EMULATOR_FROZEN', 0)
|
||||
RASPI = os.getenv('TREZOR_EMULATOR_RASPI') == '1'
|
||||
|
||||
if TREZOR_MODEL in ('1', 'R'):
|
||||
FONT_NORMAL='Font_PixelOperator_Regular_8'
|
||||
FONT_DEMIBOLD='Font_PixelOperator_Regular_8'
|
||||
FONT_NORMAL='Font_Unifont_Regular_16'
|
||||
FONT_DEMIBOLD='Font_Unifont_Bold_16'
|
||||
FONT_BOLD='Font_PixelOperator_Bold_8'
|
||||
FONT_MONO='Font_PixelOperatorMono_Regular_8'
|
||||
if TREZOR_MODEL in ('T', ):
|
||||
|
BIN
core/assets/model_r/delete.png
Normal file
BIN
core/assets/model_r/delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 135 B |
BIN
core/assets/model_r/eye.png
Normal file
BIN
core/assets/model_r/eye.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 B |
BIN
core/assets/model_r/tick.png
Normal file
BIN
core/assets/model_r/tick.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 117 B |
@ -193,7 +193,7 @@ void display_init(void) {
|
||||
#include "background_T.h"
|
||||
BACKGROUND = IMG_LoadTexture_RW(
|
||||
RENDERER, SDL_RWFromMem(background_T_jpg, background_T_jpg_len), 0);
|
||||
#elif defined TREZOR_MODEL_1
|
||||
#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
|
||||
#include "background_1.h"
|
||||
BACKGROUND = IMG_LoadTexture_RW(
|
||||
RENDERER, SDL_RWFromMem(background_1_jpg, background_1_jpg_len), 0);
|
||||
|
@ -29,7 +29,7 @@
|
||||
#define DISPLAY_RESY 240
|
||||
#define TREZOR_FONT_BPP 4
|
||||
|
||||
#elif defined TREZOR_MODEL_1
|
||||
#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R
|
||||
|
||||
#define MAX_DISPLAY_RESX 128
|
||||
#define MAX_DISPLAY_RESY 64
|
||||
@ -37,14 +37,6 @@
|
||||
#define DISPLAY_RESY 64
|
||||
#define TREZOR_FONT_BPP 1
|
||||
|
||||
#elif defined TREZOR_MODEL_R
|
||||
|
||||
#define MAX_DISPLAY_RESX 128
|
||||
#define MAX_DISPLAY_RESY 128
|
||||
#define DISPLAY_RESX 128
|
||||
#define DISPLAY_RESY 128
|
||||
#define TREZOR_FONT_BPP 1
|
||||
|
||||
#else
|
||||
#error Unknown Trezor model
|
||||
#endif
|
||||
|
@ -6,7 +6,7 @@
|
||||
// - the third, fourth and fifth bytes are advance, bearingX and bearingY of the horizontal metrics of the glyph
|
||||
// - the rest is packed 1-bit glyph data
|
||||
|
||||
/* */ static const uint8_t Font_Unifont_Bold_16_glyph_32[] = { 0, 0, 7, 0, 0 }; // hand-changed to 7 to have 9px space between words
|
||||
/* */ static const uint8_t Font_Unifont_Bold_16_glyph_32[] = { 0, 0, 8, 0, 0 };
|
||||
/* ! */ static const uint8_t Font_Unifont_Bold_16_glyph_33[] = { 2, 10, 7, 2, 10, 255, 252, 240 };
|
||||
/* " */ static const uint8_t Font_Unifont_Bold_16_glyph_34[] = { 6, 4, 7, 0, 12, 207, 60, 209, 0 };
|
||||
/* # */ static const uint8_t Font_Unifont_Bold_16_glyph_35[] = { 7, 10, 8, 0, 10, 54, 108, 223, 246, 205, 191, 236, 217, 176 };
|
@ -3,5 +3,8 @@
|
||||
#if TREZOR_FONT_BPP != 1
|
||||
#error Wrong TREZOR_FONT_BPP (expected 1)
|
||||
#endif
|
||||
#define Font_Unifont_Bold_16_HEIGHT 16
|
||||
#define Font_Unifont_Bold_16_MAX_HEIGHT 15
|
||||
#define Font_Unifont_Bold_16_BASELINE 2
|
||||
extern const uint8_t* const Font_Unifont_Bold_16[126 + 1 - 32];
|
||||
extern const uint8_t Font_Unifont_Bold_16_glyph_nonprintable[];
|
@ -3,5 +3,8 @@
|
||||
#if TREZOR_FONT_BPP != 1
|
||||
#error Wrong TREZOR_FONT_BPP (expected 1)
|
||||
#endif
|
||||
#define Font_Unifont_Regular_16_HEIGHT 16
|
||||
#define Font_Unifont_Regular_16_MAX_HEIGHT 15
|
||||
#define Font_Unifont_Regular_16_BASELINE 2
|
||||
extern const uint8_t* const Font_Unifont_Regular_16[126 + 1 - 32];
|
||||
extern const uint8_t Font_Unifont_Regular_16_glyph_nonprintable[];
|
@ -1,8 +1,8 @@
|
||||
pub mod color;
|
||||
pub mod font;
|
||||
pub mod icon;
|
||||
#[cfg(any(feature = "model_tt", feature = "model_tr"))]
|
||||
pub mod loader;
|
||||
pub mod icon;
|
||||
pub mod font;
|
||||
pub mod color;
|
||||
|
||||
use super::{
|
||||
constant,
|
||||
@ -29,11 +29,11 @@ use crate::{
|
||||
};
|
||||
|
||||
// Reexports
|
||||
pub use color::Color;
|
||||
pub use font::{Font, Glyph};
|
||||
pub use icon::{Icon, IconAndName};
|
||||
#[cfg(any(feature = "model_tt", feature = "model_tr"))]
|
||||
pub use loader::{loader, loader_indeterminate, LOADER_MAX, LOADER_MIN};
|
||||
pub use icon::{Icon, IconAndName};
|
||||
pub use font::{Font, Glyph};
|
||||
pub use color::Color;
|
||||
|
||||
pub fn backlight() -> i32 {
|
||||
display::backlight(-1)
|
||||
@ -113,7 +113,8 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
|
||||
icon_rect(r, toif_data, fg_color, bg_color);
|
||||
}
|
||||
|
||||
/// Display icon at a specified Rectangle, expects already sliced data without header.
|
||||
/// Display icon at a specified Rectangle, expects already sliced data without
|
||||
/// header.
|
||||
pub fn icon_rect(r: Rect, toif_data: &[u8], fg_color: Color, bg_color: Color) {
|
||||
display::icon(
|
||||
r.x0,
|
||||
@ -211,6 +212,7 @@ pub fn rect_outline_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u
|
||||
if radius == 1 {
|
||||
rect_fill_rounded(r, fg_color, bg_color, 1);
|
||||
rect_fill(inner_r, bg_color);
|
||||
rect_fill_corners(inner_r, fg_color);
|
||||
} else if radius == 2 {
|
||||
rect_fill_rounded(r, fg_color, bg_color, 2);
|
||||
rect_fill_rounded(inner_r, bg_color, fg_color, 1);
|
||||
|
@ -309,6 +309,10 @@ impl Rect {
|
||||
self.bottom_left().center(self.bottom_right())
|
||||
}
|
||||
|
||||
pub const fn top_center(&self) -> Point {
|
||||
self.top_left().center(self.top_right())
|
||||
}
|
||||
|
||||
pub const fn left_center(&self) -> Point {
|
||||
self.bottom_left().center(self.top_left())
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ use crate::{
|
||||
ui::{
|
||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||
geometry::Rect,
|
||||
util::char_to_string,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
choice_item::BigCharacterChoiceItem, ButtonDetails, ButtonLayout, ChangingTextLine,
|
||||
ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, TextChoiceItem,
|
||||
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
|
||||
ChoicePageMsg,
|
||||
};
|
||||
use heapless::{String, Vec};
|
||||
|
||||
@ -16,14 +17,14 @@ pub enum Bip39EntryMsg {
|
||||
ResultWord(String<15>),
|
||||
}
|
||||
|
||||
const CURRENT_LETTERS_ROW: i32 = 25;
|
||||
|
||||
const MAX_LENGTH: usize = 10;
|
||||
const MAX_CHOICE_LENGTH: usize = 26;
|
||||
|
||||
/// Offer words when there will be fewer of them than this
|
||||
const OFFER_WORDS_THRESHOLD: usize = 10;
|
||||
|
||||
const PROMPT: &str = "_";
|
||||
|
||||
struct ChoiceFactoryBIP39 {
|
||||
// TODO: replace these Vecs by iterators somehow?
|
||||
letter_choices: Option<Vec<char, MAX_CHOICE_LENGTH>>,
|
||||
@ -52,13 +53,14 @@ impl ChoiceFactoryBIP39 {
|
||||
fn get_word_item(&self, choice_index: u8) -> ChoiceItem {
|
||||
if let Some(word_choices) = &self.word_choices {
|
||||
let word = word_choices[choice_index as usize];
|
||||
let choice = TextChoiceItem::new(word, ButtonLayout::default_three_icons());
|
||||
let mut word_item = ChoiceItem::Text(choice);
|
||||
let mut word_item = ChoiceItem::new(word, ButtonLayout::default_three_icons());
|
||||
|
||||
// Adding BIN leftmost button and removing the rightmost one.
|
||||
// Adding BIN leftmost button.
|
||||
if choice_index == 0 {
|
||||
word_item.set_left_btn(Some(ButtonDetails::bin_icon()));
|
||||
} else if choice_index as usize == word_choices.len() - 1 {
|
||||
}
|
||||
// Removing the rightmost button.
|
||||
if choice_index as usize == word_choices.len() - 1 {
|
||||
word_item.set_right_btn(None);
|
||||
}
|
||||
|
||||
@ -75,14 +77,17 @@ impl ChoiceFactoryBIP39 {
|
||||
// user-friendly)
|
||||
if let Some(letter_choices) = &self.letter_choices {
|
||||
let letter = letter_choices[choice_index as usize];
|
||||
let letter_choice =
|
||||
BigCharacterChoiceItem::new(letter, ButtonLayout::default_three_icons());
|
||||
let mut letter_item = ChoiceItem::BigCharacter(letter_choice);
|
||||
let mut letter_item = ChoiceItem::new(
|
||||
char_to_string::<1>(letter),
|
||||
ButtonLayout::default_three_icons(),
|
||||
);
|
||||
|
||||
// Adding BIN leftmost button and removing the rightmost one.
|
||||
// Adding BIN leftmost button.
|
||||
if choice_index == 0 {
|
||||
letter_item.set_left_btn(Some(ButtonDetails::bin_icon()));
|
||||
} else if choice_index as usize == letter_choices.len() - 1 {
|
||||
}
|
||||
// Removing the rightmost button.
|
||||
if choice_index as usize == letter_choices.len() - 1 {
|
||||
letter_item.set_right_btn(None);
|
||||
}
|
||||
|
||||
@ -131,8 +136,8 @@ impl Bip39Entry {
|
||||
let choices = ChoiceFactoryBIP39::letters(letter_choices.clone());
|
||||
|
||||
Self {
|
||||
choice_page: ChoicePage::new(choices),
|
||||
chosen_letters: Child::new(ChangingTextLine::center_mono(String::new())),
|
||||
choice_page: ChoicePage::new(choices).with_incomplete(),
|
||||
chosen_letters: Child::new(ChangingTextLine::center_mono(String::from(PROMPT))),
|
||||
letter_choices,
|
||||
textbox: TextBox::empty(),
|
||||
offer_words: false,
|
||||
@ -159,7 +164,7 @@ impl Bip39Entry {
|
||||
}
|
||||
|
||||
fn update_chosen_letters(&mut self, ctx: &mut EventCtx) {
|
||||
let text = build_string!({ MAX_LENGTH + 1 }, self.textbox.content(), "_");
|
||||
let text = build_string!({ MAX_LENGTH + 1 }, self.textbox.content(), PROMPT);
|
||||
self.chosen_letters.inner_mut().update_text(text);
|
||||
self.chosen_letters.request_complete_repaint(ctx);
|
||||
}
|
||||
|
@ -233,10 +233,18 @@ where
|
||||
text_color,
|
||||
background_color,
|
||||
);
|
||||
display::rect_fill_corners(area_to_fill, theme::BG);
|
||||
} else if style.with_outline {
|
||||
if background_color == theme::BG {
|
||||
display::rect_outline_rounded(area, text_color, background_color, 2);
|
||||
} else {
|
||||
display::rect_fill(area, background_color)
|
||||
// With inverse colors having just radius of one, `rect_outline_rounded`
|
||||
// is not suitable for inverse colors.
|
||||
display::rect_fill(area, background_color);
|
||||
display::rect_fill_corners(area, theme::BG);
|
||||
}
|
||||
} else {
|
||||
display::rect_fill(area, background_color);
|
||||
}
|
||||
|
||||
match &self.content {
|
||||
|
@ -23,6 +23,7 @@ enum ButtonState {
|
||||
}
|
||||
|
||||
pub enum ButtonControllerMsg {
|
||||
Pressed(ButtonPos),
|
||||
Triggered(ButtonPos),
|
||||
}
|
||||
|
||||
@ -275,16 +276,18 @@ impl<T: Clone + AsRef<str>> Component for ButtonController<T> {
|
||||
let (new_state, event) = match self.state {
|
||||
ButtonState::Nothing => match button {
|
||||
ButtonEvent::ButtonPressed(which) => {
|
||||
match which {
|
||||
let event = match which {
|
||||
PhysicalButton::Left => {
|
||||
self.left_btn.hold_started(ctx);
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Left))
|
||||
}
|
||||
PhysicalButton::Right => {
|
||||
self.right_btn.hold_started(ctx);
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Right))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
(ButtonState::OneDown(which), None)
|
||||
_ => None,
|
||||
};
|
||||
(ButtonState::OneDown(which), event)
|
||||
}
|
||||
_ => (self.state, None),
|
||||
},
|
||||
@ -313,7 +316,10 @@ impl<T: Clone + AsRef<str>> Component for ButtonController<T> {
|
||||
|
||||
ButtonEvent::ButtonPressed(b) if b != which_down => {
|
||||
self.middle_hold_started(ctx);
|
||||
(ButtonState::BothDown, None)
|
||||
(
|
||||
ButtonState::BothDown,
|
||||
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
|
||||
)
|
||||
}
|
||||
_ => (self.state, None),
|
||||
},
|
||||
|
@ -3,7 +3,7 @@ use crate::ui::{
|
||||
geometry::Rect,
|
||||
};
|
||||
|
||||
use super::{theme, ButtonController, ButtonControllerMsg, ButtonPos, ChoiceItem, ChoiceItemAPI};
|
||||
use super::{theme, ButtonController, ButtonControllerMsg, ButtonPos, ChoiceItem};
|
||||
|
||||
pub enum ChoicePageMsg {
|
||||
Choice(u8),
|
||||
@ -11,7 +11,8 @@ pub enum ChoicePageMsg {
|
||||
RightMost,
|
||||
}
|
||||
|
||||
const MIDDLE_ROW: i32 = 72;
|
||||
const DEFAULT_ITEMS_DISTANCE: i16 = 10;
|
||||
const DEFAULT_Y_BASELINE: i16 = 20;
|
||||
|
||||
/// Interface for a specific component efficiently giving
|
||||
/// `ChoicePage` all the information it needs to render
|
||||
@ -48,7 +49,18 @@ where
|
||||
pad: Pad,
|
||||
buttons: Child<ButtonController<&'static str>>,
|
||||
page_counter: u8,
|
||||
/// How many pixels from top should we render the items.
|
||||
y_baseline: i16,
|
||||
/// How many pixels are between the items.
|
||||
items_distance: i16,
|
||||
/// Whether the choice page is "infinite" (carousel).
|
||||
is_carousel: bool,
|
||||
/// Whether we should show items on left/right even when they cannot
|
||||
/// be painted entirely (they would be cut off).
|
||||
show_incomplete: bool,
|
||||
/// Whether the middle selected item should be painted with
|
||||
/// inverse colors - black on white.
|
||||
inverse_selected_item: bool,
|
||||
}
|
||||
|
||||
impl<F> ChoicePage<F>
|
||||
@ -63,7 +75,11 @@ where
|
||||
pad: Pad::with_background(theme::BG),
|
||||
buttons: Child::new(ButtonController::new(initial_btn_layout)),
|
||||
page_counter: 0,
|
||||
y_baseline: DEFAULT_Y_BASELINE,
|
||||
items_distance: DEFAULT_ITEMS_DISTANCE,
|
||||
is_carousel: false,
|
||||
show_incomplete: false,
|
||||
inverse_selected_item: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,9 +95,30 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Show incomplete items, even when they cannot render in their entirety.
|
||||
pub fn with_incomplete(mut self) -> Self {
|
||||
self.show_incomplete = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the horizontal baseline from the top of placement.
|
||||
pub fn with_y_baseline(mut self, y_baseline: i16) -> Self {
|
||||
self.y_baseline = y_baseline;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the distance between the items.
|
||||
pub fn with_items_distance(mut self, items_distance: i16) -> Self {
|
||||
self.items_distance = items_distance;
|
||||
self
|
||||
}
|
||||
|
||||
/// Resetting the component, which enables reusing the same instance
|
||||
/// for multiple choice categories.
|
||||
///
|
||||
/// Used for example in passphrase, where there are multiple categories of
|
||||
/// characters.
|
||||
///
|
||||
/// NOTE: from the client point of view, it would also be an option to
|
||||
/// always create a new instance with fresh setup, but I could not manage to
|
||||
/// properly clean up the previous instance - it would still be shown on
|
||||
@ -97,8 +134,8 @@ where
|
||||
if reset_page_counter {
|
||||
self.page_counter = 0;
|
||||
}
|
||||
self.update(ctx);
|
||||
self.is_carousel = is_carousel;
|
||||
self.update(ctx);
|
||||
}
|
||||
|
||||
/// Navigating to the chosen page index.
|
||||
@ -107,24 +144,26 @@ where
|
||||
self.update(ctx);
|
||||
}
|
||||
|
||||
/// Display current, previous and next choice according to
|
||||
/// Display current, previous and next choices according to
|
||||
/// the current ChoiceItem.
|
||||
fn paint_choices(&mut self) {
|
||||
// Performing the appropriate `paint_XXX()` for the main choice
|
||||
// and two adjacent choices when present
|
||||
// In case of carousel mode, also showing the ones from other end.
|
||||
self.show_current_choice();
|
||||
let available_area = self.pad.area.split_top(self.y_baseline).0;
|
||||
|
||||
if self.has_previous_choice() {
|
||||
self.show_previous_choice();
|
||||
} else if self.is_carousel {
|
||||
self.show_last_choice_on_left();
|
||||
// Drawing the current item in the middle.
|
||||
self.show_current_choice(available_area);
|
||||
|
||||
// Getting the remaining left and right areas.
|
||||
let (left_area, _center_area, right_area) =
|
||||
available_area.split_center(self.current_choice().width_center());
|
||||
|
||||
// Possibly drawing on the left side.
|
||||
if self.has_previous_choice() || self.is_carousel {
|
||||
self.show_left_choices(left_area);
|
||||
}
|
||||
|
||||
if self.has_next_choice() {
|
||||
self.show_next_choice();
|
||||
} else if self.is_carousel {
|
||||
self.show_first_choice_on_right();
|
||||
// Possibly drawing on the right side.
|
||||
if self.has_next_choice() || self.is_carousel {
|
||||
self.show_right_choices(right_area);
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,62 +179,139 @@ where
|
||||
ctx.request_paint();
|
||||
}
|
||||
|
||||
/// Index of the last page.
|
||||
fn last_page_index(&self) -> u8 {
|
||||
self.choices.count() as u8 - 1
|
||||
}
|
||||
|
||||
/// Whether there is a previous choice (on the left).
|
||||
pub fn has_previous_choice(&self) -> bool {
|
||||
self.page_counter > 0
|
||||
}
|
||||
|
||||
/// Whether there is a next choice (on the right).
|
||||
pub fn has_next_choice(&self) -> bool {
|
||||
self.page_counter < self.last_page_index()
|
||||
}
|
||||
|
||||
/// Gets choice at the current page index.
|
||||
fn current_choice(&self) -> ChoiceItem {
|
||||
self.get_choice(self.page_counter)
|
||||
}
|
||||
|
||||
/// Gets choice at the given page index.
|
||||
fn get_choice(&self, index: u8) -> ChoiceItem {
|
||||
self.choices.get(index)
|
||||
}
|
||||
|
||||
fn show_current_choice(&self) {
|
||||
self.current_choice().paint_center();
|
||||
/// Display the current choice in the middle.
|
||||
fn show_current_choice(&mut self, area: Rect) {
|
||||
self.current_choice()
|
||||
.paint_center(area, self.inverse_selected_item);
|
||||
|
||||
// Color inversion is just one-time thing.
|
||||
if self.inverse_selected_item {
|
||||
self.inverse_selected_item = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn show_previous_choice(&self) {
|
||||
self.get_choice(self.page_counter - 1).paint_left();
|
||||
/// Display all the choices fitting on the left side.
|
||||
/// Going as far as possible.
|
||||
fn show_left_choices(&self, area: Rect) {
|
||||
let mut page_index = self.page_counter as i16 - 1;
|
||||
let mut x_offset = 0;
|
||||
loop {
|
||||
// Breaking out of the loop if we exhausted left items
|
||||
// and the carousel mode is not enabled.
|
||||
if page_index < 0 {
|
||||
if self.is_carousel {
|
||||
// Moving to the last page.
|
||||
page_index = self.last_page_index() as i16;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn show_next_choice(&self) {
|
||||
self.get_choice(self.page_counter + 1).paint_right();
|
||||
let current_choice = self.get_choice(page_index as u8);
|
||||
let current_area = area.split_right(x_offset + self.items_distance).0;
|
||||
|
||||
// When the item does not fit, we stop.
|
||||
// Rendering the item anyway if the incomplete items are allowed.
|
||||
if !current_choice.fits(current_area) {
|
||||
if self.show_incomplete {
|
||||
current_choice.paint_left(current_area);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
fn show_last_choice_on_left(&self) {
|
||||
self.get_choice(self.last_page_index()).paint_left();
|
||||
// Rendering the item.
|
||||
current_choice.paint_left(current_area);
|
||||
|
||||
// Updating loop variables.
|
||||
x_offset += current_choice.width_side() + self.items_distance;
|
||||
page_index -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn show_first_choice_on_right(&self) {
|
||||
self.get_choice(0).paint_right();
|
||||
/// Display all the choices fitting on the right side.
|
||||
/// Going as far as possible.
|
||||
fn show_right_choices(&self, area: Rect) {
|
||||
let mut page_index = self.page_counter + 1;
|
||||
let mut x_offset = 3; // starts with a little offset to account for the middle highlight
|
||||
loop {
|
||||
// Breaking out of the loop if we exhausted right items
|
||||
// and the carousel mode is not enabled.
|
||||
if page_index > self.last_page_index() {
|
||||
if self.is_carousel {
|
||||
// Moving to the first page.
|
||||
page_index = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let current_choice = self.get_choice(page_index);
|
||||
let current_area = area.split_left(x_offset + self.items_distance).1;
|
||||
|
||||
// When the item does not fit, we stop.
|
||||
// Rendering the item anyway if the incomplete items are allowed.
|
||||
if !current_choice.fits(current_area) {
|
||||
if self.show_incomplete {
|
||||
current_choice.paint_right(current_area);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Rendering the item.
|
||||
current_choice.paint_right(current_area);
|
||||
|
||||
// Updating loop variables.
|
||||
x_offset += current_choice.width_side() + self.items_distance;
|
||||
page_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Decrease the page counter to the previous page.
|
||||
fn decrease_page_counter(&mut self) {
|
||||
self.page_counter -= 1;
|
||||
}
|
||||
|
||||
/// Advance page counter to the next page.
|
||||
fn increase_page_counter(&mut self) {
|
||||
self.page_counter += 1;
|
||||
}
|
||||
|
||||
/// Set page to the first one.
|
||||
fn page_counter_to_zero(&mut self) {
|
||||
self.page_counter = 0;
|
||||
}
|
||||
|
||||
/// Set page to the last one.
|
||||
fn page_counter_to_max(&mut self) {
|
||||
self.page_counter = self.last_page_index();
|
||||
}
|
||||
|
||||
/// Get current page counter.
|
||||
pub fn page_index(&self) -> u8 {
|
||||
self.page_counter
|
||||
}
|
||||
@ -238,6 +354,7 @@ where
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let button_event = self.buttons.event(ctx, event);
|
||||
|
||||
// Button was "triggered" - released. Doing the appropriate action.
|
||||
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
|
||||
match pos {
|
||||
ButtonPos::Left => {
|
||||
@ -277,6 +394,12 @@ where
|
||||
}
|
||||
}
|
||||
};
|
||||
// The middle button was "pressed", highlighting the current choice by color
|
||||
// inversion.
|
||||
if let Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)) = button_event {
|
||||
self.inverse_selected_item = true;
|
||||
self.clear(ctx);
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -1,338 +1,172 @@
|
||||
use crate::ui::{geometry::Point, display::Font, util::char_to_string};
|
||||
use crate::ui::{
|
||||
display::{rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon},
|
||||
geometry::{Offset, Rect},
|
||||
model_tr::theme,
|
||||
};
|
||||
use heapless::String;
|
||||
|
||||
use super::{
|
||||
common::{display, display_center, display_right},
|
||||
common::{display, display_inverse, display_right},
|
||||
ButtonDetails, ButtonLayout,
|
||||
};
|
||||
|
||||
const MIDDLE_ROW: i16 = 61;
|
||||
const LEFT_COL: i16 = 1;
|
||||
const MIDDLE_COL: i16 = 64;
|
||||
const RIGHT_COL: i16 = 127;
|
||||
|
||||
/// Helper to unite the row height.
|
||||
fn row_height() -> i16 {
|
||||
// It never reaches the maximum height
|
||||
Font::NORMAL.line_height() - 4
|
||||
}
|
||||
|
||||
/// Component that can be used as a choice item.
|
||||
/// Allows to have a choice of anything that can be painted on screen.
|
||||
///
|
||||
/// Controls the painting of the current, previous and next item
|
||||
/// through `paint_XXX()` methods.
|
||||
/// Defines the behavior of all three buttons through `btn_XXX` attributes.
|
||||
///
|
||||
/// Possible implementations:
|
||||
/// - [x] `TextChoiceItem` - for regular text
|
||||
/// - [x] `MultilineTextChoiceItem` - for multiline text
|
||||
/// - [x] `BigCharacterChoiceItem` - for one big character
|
||||
/// - [ ] `IconChoiceItem` - for showing icons
|
||||
/// - [ ] `JustCenterChoice` - paint_left() and paint_right() show nothing
|
||||
/// - [ ] `LongStringsChoice` - paint_left() and paint_right() show ellipsis
|
||||
pub trait ChoiceItemAPI {
|
||||
fn paint_center(&mut self);
|
||||
fn paint_left(&mut self);
|
||||
fn paint_right(&mut self);
|
||||
fn btn_layout(&self) -> ButtonLayout<&'static str>;
|
||||
}
|
||||
|
||||
// TODO: consider having
|
||||
// pub trait ChoiceItemOperations {}
|
||||
|
||||
// TODO: consider storing all the text components as `T: AsRef<str>`
|
||||
// Tried, but it makes the code unnecessarily messy with all the <T>
|
||||
// definitions, which needs to be added to all the components using it.
|
||||
|
||||
/// Storing all the possible implementations of `ChoiceItemAPI`.
|
||||
/// Done like this as we want to use multiple different choice pages
|
||||
/// at the same time in `ChoicePage` - for example Multiline and BigLetters
|
||||
/// Simple string component used as a choice item.
|
||||
#[derive(Clone)]
|
||||
pub enum ChoiceItem {
|
||||
Text(TextChoiceItem),
|
||||
MultilineText(MultilineTextChoiceItem),
|
||||
BigCharacter(BigCharacterChoiceItem),
|
||||
pub struct ChoiceItem {
|
||||
text: String<50>,
|
||||
icon: Option<Icon>,
|
||||
btn_layout: ButtonLayout<&'static str>,
|
||||
font: Font,
|
||||
}
|
||||
|
||||
impl ChoiceItem {
|
||||
// TODO: can we somehow avoid the repetitions here?
|
||||
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<&'static str>>) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.btn_layout.btn_left = btn_left,
|
||||
ChoiceItem::MultilineText(item) => item.btn_layout.btn_left = btn_left,
|
||||
ChoiceItem::BigCharacter(item) => item.btn_layout.btn_left = btn_left,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_middle_btn(&mut self, btn_middle: Option<ButtonDetails<&'static str>>) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.btn_layout.btn_middle = btn_middle,
|
||||
ChoiceItem::MultilineText(item) => item.btn_layout.btn_middle = btn_middle,
|
||||
ChoiceItem::BigCharacter(item) => item.btn_layout.btn_middle = btn_middle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_right_btn(&mut self, btn_right: Option<ButtonDetails<&'static str>>) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.btn_layout.btn_right = btn_right,
|
||||
ChoiceItem::MultilineText(item) => item.btn_layout.btn_right = btn_right,
|
||||
ChoiceItem::BigCharacter(item) => item.btn_layout.btn_right = btn_right,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: String<50>) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.text = text,
|
||||
ChoiceItem::MultilineText(item) => item.text = text,
|
||||
ChoiceItem::BigCharacter(_) => {
|
||||
panic!("No text setting for BigCharacter")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChoiceItemAPI for ChoiceItem {
|
||||
fn paint_center(&mut self) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.paint_center(),
|
||||
ChoiceItem::MultilineText(item) => item.paint_center(),
|
||||
ChoiceItem::BigCharacter(item) => item.paint_center(),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_left(&mut self) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.paint_left(),
|
||||
ChoiceItem::MultilineText(item) => item.paint_left(),
|
||||
ChoiceItem::BigCharacter(item) => item.paint_left(),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_right(&mut self) {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.paint_right(),
|
||||
ChoiceItem::MultilineText(item) => item.paint_right(),
|
||||
ChoiceItem::BigCharacter(item) => item.paint_right(),
|
||||
}
|
||||
}
|
||||
|
||||
fn btn_layout(&self) -> ButtonLayout<&'static str> {
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.btn_layout(),
|
||||
ChoiceItem::MultilineText(item) => item.btn_layout(),
|
||||
ChoiceItem::BigCharacter(item) => item.btn_layout(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple string component used as a choice item.
|
||||
#[derive(Clone)]
|
||||
pub struct TextChoiceItem {
|
||||
pub text: String<50>,
|
||||
pub btn_layout: ButtonLayout<&'static str>,
|
||||
}
|
||||
|
||||
impl TextChoiceItem {
|
||||
pub fn new<T>(text: T, btn_layout: ButtonLayout<&'static str>) -> Self
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
Self {
|
||||
text: String::from(text.as_ref()),
|
||||
icon: None,
|
||||
btn_layout,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChoiceItemAPI for TextChoiceItem {
|
||||
fn paint_center(&mut self) {
|
||||
// Displaying the center choice lower than the rest,
|
||||
// to make it more clear this is the current choice
|
||||
// (and also the left and right ones do not collide with it)
|
||||
display_center(
|
||||
Point::new(MIDDLE_COL, MIDDLE_ROW + row_height()),
|
||||
&self.text,
|
||||
Font::NORMAL,
|
||||
);
|
||||
}
|
||||
|
||||
fn paint_left(&mut self) {
|
||||
display(
|
||||
Point::new(LEFT_COL, MIDDLE_ROW),
|
||||
&self.text,
|
||||
Font::NORMAL,
|
||||
);
|
||||
}
|
||||
|
||||
fn paint_right(&mut self) {
|
||||
display_right(
|
||||
Point::new(RIGHT_COL, MIDDLE_ROW),
|
||||
&self.text,
|
||||
Font::NORMAL,
|
||||
);
|
||||
}
|
||||
|
||||
fn btn_layout(&self) -> ButtonLayout<&'static str> {
|
||||
self.btn_layout.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiline string component used as a choice item.
|
||||
///
|
||||
/// Lines are delimited by '\n' character, unless specified explicitly.
|
||||
#[derive(Clone)]
|
||||
pub struct MultilineTextChoiceItem {
|
||||
// Arbitrary chosen. TODO: agree on this
|
||||
pub text: String<50>,
|
||||
delimiter: char,
|
||||
pub btn_layout: ButtonLayout<&'static str>,
|
||||
}
|
||||
|
||||
impl MultilineTextChoiceItem {
|
||||
pub fn new(text: String<50>, btn_layout: ButtonLayout<&'static str>) -> Self {
|
||||
Self {
|
||||
text,
|
||||
delimiter: '\n',
|
||||
btn_layout,
|
||||
font: theme::FONT_CHOICE_ITEMS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows for changing the line delimiter to arbitrary char.
|
||||
pub fn use_delimiter(mut self, delimiter: char) -> Self {
|
||||
self.delimiter = delimiter;
|
||||
/// Allows to add the icon.
|
||||
pub fn with_icon(mut self, icon: Icon) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make all the text be centered vertically - account for amount of lines.
|
||||
impl ChoiceItemAPI for MultilineTextChoiceItem {
|
||||
fn paint_center(&mut self) {
|
||||
// Displaying the center choice lower than the rest,
|
||||
// to make it more clear this is the current choice
|
||||
for (index, line) in self.text.split(self.delimiter).enumerate() {
|
||||
let offset = MIDDLE_ROW + index as i16 * row_height() + row_height();
|
||||
display_center(Point::new(MIDDLE_COL, offset), &line, Font::NORMAL);
|
||||
/// Allows to change the font.
|
||||
pub fn with_font(mut self, font: Font) -> Self {
|
||||
self.font = font;
|
||||
self
|
||||
}
|
||||
|
||||
/// Getting the text width in pixels.
|
||||
pub fn text_width(&self) -> i16 {
|
||||
self.font.text_width(&self.text)
|
||||
}
|
||||
|
||||
/// Getting the overall width in pixels when displayed in center.
|
||||
/// That means both the icon and text will be shown.
|
||||
pub fn width_center(&self) -> i16 {
|
||||
let icon_width = if let Some(icon) = self.icon {
|
||||
icon.width() + 2
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.text_width() + icon_width
|
||||
}
|
||||
|
||||
/// Getting the non-central width in pixels.
|
||||
/// It will show an icon if defined, otherwise the text, not both.
|
||||
pub fn width_side(&self) -> i16 {
|
||||
if let Some(icon) = self.icon {
|
||||
icon.width()
|
||||
} else {
|
||||
self.text_width()
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_left(&mut self) {
|
||||
for (index, line) in self.text.split(self.delimiter).enumerate() {
|
||||
let offset = MIDDLE_ROW + index as i16 * row_height();
|
||||
display(Point::new(LEFT_COL, offset), &line, Font::NORMAL);
|
||||
}
|
||||
/// Whether the whole item fits into the given rectangle.
|
||||
pub fn fits(&self, rect: Rect) -> bool {
|
||||
self.width_side() <= rect.width()
|
||||
}
|
||||
|
||||
fn paint_right(&mut self) {
|
||||
for (index, line) in self.text.split(self.delimiter).enumerate() {
|
||||
let offset = MIDDLE_ROW + index as i16 * row_height();
|
||||
display_right(Point::new(RIGHT_COL, offset), &line, Font::NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
fn btn_layout(&self) -> ButtonLayout<&'static str> {
|
||||
self.btn_layout.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Choice item displaying single characters in BIG font.
|
||||
/// Middle choice is magnified 4 times, left and right 2 times.
|
||||
#[derive(Clone)]
|
||||
pub struct BigCharacterChoiceItem {
|
||||
pub ch: char,
|
||||
pub btn_layout: ButtonLayout<&'static str>,
|
||||
}
|
||||
|
||||
impl BigCharacterChoiceItem {
|
||||
pub fn new(ch: char, btn_layout: ButtonLayout<&'static str>) -> Self {
|
||||
Self { ch, btn_layout }
|
||||
}
|
||||
|
||||
/// Taking the first character from the `text`.
|
||||
pub fn from_str<T>(text: T, btn_layout: ButtonLayout<&'static str>) -> Self
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
Self {
|
||||
ch: text.as_ref().chars().next().unwrap(),
|
||||
btn_layout,
|
||||
}
|
||||
}
|
||||
|
||||
fn _paint_char(&mut self, baseline: Point) {
|
||||
display(
|
||||
baseline,
|
||||
&char_to_string::<1>(self.ch),
|
||||
Font::NORMAL,
|
||||
/// Draws highlight around this choice item.
|
||||
/// Must be called before the item is drawn, otherwise it will
|
||||
/// cover the item.
|
||||
pub fn paint_rounded_highlight(&self, area: Rect, inverse: bool) {
|
||||
let bound = 3;
|
||||
let left_bottom =
|
||||
area.bottom_center() + Offset::new(-self.width_center() / 2 - bound, bound + 1);
|
||||
let outline_size = Offset::new(
|
||||
self.width_center() + 2 * bound,
|
||||
self.font.text_height() + 2 * bound - 3, // -3 because font is actually smaller
|
||||
);
|
||||
let outline = Rect::from_bottom_left_and_size(left_bottom, outline_size);
|
||||
if inverse {
|
||||
rect_fill(outline, theme::FG);
|
||||
rect_fill_corners(outline, theme::BG);
|
||||
} else {
|
||||
rect_outline_rounded(outline, theme::FG, theme::BG, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/// Painting the item as the main choice in the middle.
|
||||
/// Showing both the icon and text, if the icon is available.
|
||||
pub fn paint_center(&self, area: Rect, inverse: bool) {
|
||||
self.paint_rounded_highlight(area, inverse);
|
||||
|
||||
impl ChoiceItemAPI for BigCharacterChoiceItem {
|
||||
fn paint_center(&mut self) {
|
||||
self._paint_char(Point::new(MIDDLE_COL - 12, MIDDLE_ROW + 9));
|
||||
let mut baseline = area.bottom_center() + Offset::new(-self.width_center() / 2, 0);
|
||||
if let Some(icon) = self.icon {
|
||||
let fg_color = if inverse { theme::BG } else { theme::FG };
|
||||
let bg_color = if inverse { theme::FG } else { theme::BG };
|
||||
icon.draw_bottom_left(baseline, fg_color, bg_color);
|
||||
baseline = baseline + Offset::new(icon.width() + 2, 0);
|
||||
}
|
||||
if inverse {
|
||||
display_inverse(baseline, &self.text, self.font);
|
||||
} else {
|
||||
display(baseline, &self.text, self.font);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_left(&mut self) {
|
||||
self._paint_char(Point::new(LEFT_COL, MIDDLE_ROW));
|
||||
/// Painting the item as a choice on the left side from center.
|
||||
/// Showing only the icon, if available, otherwise the text.
|
||||
pub fn paint_left(&self, area: Rect) {
|
||||
if let Some(icon) = self.icon {
|
||||
icon.draw_bottom_right(area.bottom_right(), theme::FG, theme::BG);
|
||||
} else {
|
||||
display_right(area.bottom_right(), &self.text, self.font);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_right(&mut self) {
|
||||
self._paint_char(Point::new(RIGHT_COL - 12, MIDDLE_ROW));
|
||||
/// Painting the item as a choice on the right side from center.
|
||||
/// Showing only the icon, if available, otherwise the text.
|
||||
pub fn paint_right(&self, area: Rect) {
|
||||
if let Some(icon) = self.icon {
|
||||
icon.draw_bottom_left(area.bottom_left(), theme::FG, theme::BG);
|
||||
} else {
|
||||
display(area.bottom_left(), &self.text, self.font);
|
||||
}
|
||||
}
|
||||
|
||||
fn btn_layout(&self) -> ButtonLayout<&'static str> {
|
||||
/// Getting current button layout.
|
||||
pub fn btn_layout(&self) -> ButtonLayout<&'static str> {
|
||||
self.btn_layout.clone()
|
||||
}
|
||||
|
||||
/// Setting left button.
|
||||
pub fn set_left_btn(&mut self, btn_left: Option<ButtonDetails<&'static str>>) {
|
||||
self.btn_layout.btn_left = btn_left;
|
||||
}
|
||||
|
||||
/// Setting middle button.
|
||||
pub fn set_middle_btn(&mut self, btn_middle: Option<ButtonDetails<&'static str>>) {
|
||||
self.btn_layout.btn_middle = btn_middle;
|
||||
}
|
||||
|
||||
/// Setting right button.
|
||||
pub fn set_right_btn(&mut self, btn_right: Option<ButtonDetails<&'static str>>) {
|
||||
self.btn_layout.btn_right = btn_right;
|
||||
}
|
||||
|
||||
/// Changing the text.
|
||||
pub fn set_text(&mut self, text: String<50>) {
|
||||
self.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for ChoiceItem {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("ChoiceItem");
|
||||
match self {
|
||||
ChoiceItem::Text(item) => item.trace(t),
|
||||
ChoiceItem::MultilineText(item) => item.trace(t),
|
||||
ChoiceItem::BigCharacter(item) => item.trace(t),
|
||||
}
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for TextChoiceItem {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("TextChoiceItem");
|
||||
t.content_flag();
|
||||
t.string(&self.text);
|
||||
t.content_flag();
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
use crate::ui::util;
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for MultilineTextChoiceItem {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("MultilineTextChoiceItem");
|
||||
t.content_flag();
|
||||
t.string(&self.text);
|
||||
t.content_flag();
|
||||
t.field("delimiter", &(util::char_to_string::<1>(self.delimiter)));
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for BigCharacterChoiceItem {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("BigCharacterChoiceItem");
|
||||
t.content_flag();
|
||||
t.string(&util::char_to_string::<1>(self.ch));
|
||||
t.content_flag();
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,22 @@
|
||||
use crate::ui::{
|
||||
display::{self, Font, Icon},
|
||||
geometry::{Offset, Point},
|
||||
model_tr::constant,
|
||||
geometry::{Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use heapless::String;
|
||||
|
||||
use super::theme;
|
||||
|
||||
/// Display header text.
|
||||
pub fn display_header<T: AsRef<str>>(baseline: Point, text: T) {
|
||||
// TODO: make this centered?
|
||||
display::text(
|
||||
baseline,
|
||||
text.as_ref(),
|
||||
theme::FONT_HEADER,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
}
|
||||
|
||||
/// Display bold white text on black background
|
||||
/// Display white text on black background
|
||||
pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
display::text(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||
}
|
||||
|
||||
/// Display black text on white background
|
||||
pub fn display_inverse<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
display::text(baseline, text.as_ref(), font, theme::BG, theme::FG);
|
||||
}
|
||||
|
||||
/// Display white text on black background,
|
||||
/// centered around a baseline Point
|
||||
pub fn display_center<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||
@ -76,23 +68,19 @@ pub fn display_secret_center_top<T: AsRef<str>>(secret: T, offset_from_top: i16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Display title and possible subtitle together with a dotted line spanning
|
||||
/// the entire width.
|
||||
/// Display title/header centered at the top of the given area.
|
||||
/// Returning the painted height of the whole header.
|
||||
pub fn paint_header<T: AsRef<str>>(top_left: Point, title: T, subtitle: Option<T>) -> i16 {
|
||||
pub fn paint_header_centered<T: AsRef<str>>(title: T, area: Rect) -> i16 {
|
||||
let text_heigth = theme::FONT_HEADER.text_height();
|
||||
let title_baseline = top_left + Offset::y(text_heigth);
|
||||
display_header(title_baseline, title);
|
||||
// Optionally painting the subtitle as well
|
||||
// (and offsetting the dotted line in that case)
|
||||
let mut dotted_line_offset = text_heigth + 2;
|
||||
if let Some(subtitle) = subtitle {
|
||||
dotted_line_offset += text_heigth;
|
||||
display_header(title_baseline + Offset::y(text_heigth), subtitle);
|
||||
}
|
||||
let line_start = top_left + Offset::y(dotted_line_offset);
|
||||
display::dotted_line_horizontal(line_start, constant::WIDTH, theme::FG, 2);
|
||||
dotted_line_offset
|
||||
let title_baseline = area.top_center() + Offset::y(text_heigth);
|
||||
display::text_center(
|
||||
title_baseline,
|
||||
title.as_ref(),
|
||||
theme::FONT_HEADER,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
text_heigth
|
||||
}
|
||||
|
||||
/// Draws icon and text on the same line - icon on the left.
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
micropython::buffer::StrBuffer,
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Pad},
|
||||
geometry::{Point, Rect},
|
||||
geometry::Rect,
|
||||
},
|
||||
};
|
||||
|
||||
@ -62,8 +62,7 @@ where
|
||||
if get_new_page {
|
||||
self.current_page = self.pages.get(self.page_counter);
|
||||
}
|
||||
let content_area = self.content_area;
|
||||
self.current_page.place(content_area);
|
||||
self.current_page.place(self.content_area);
|
||||
self.set_buttons(ctx);
|
||||
self.clear(ctx);
|
||||
}
|
||||
@ -203,12 +202,13 @@ where
|
||||
|
||||
fn paint(&mut self) {
|
||||
// TODO: might put horizontal scrollbar at the top right
|
||||
// (not compatible with longer/centered titles)
|
||||
self.pad.paint();
|
||||
self.buttons.paint();
|
||||
if let Some(title) = &self.common_title {
|
||||
common::paint_header(Point::zero(), title, None);
|
||||
common::paint_header_centered(title, self.content_area);
|
||||
}
|
||||
self.current_page.paint();
|
||||
self.buttons.paint();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
micropython::{buffer::StrBuffer},
|
||||
micropython::buffer::StrBuffer,
|
||||
ui::{
|
||||
component::Paginate,
|
||||
display::{Font, Icon, IconAndName},
|
||||
geometry::{Offset, Rect},
|
||||
model_tr::theme,
|
||||
util::ResultExt
|
||||
util::ResultExt,
|
||||
},
|
||||
};
|
||||
|
||||
@ -68,9 +68,13 @@ pub struct Page<const M: usize> {
|
||||
|
||||
// For `layout.rs`
|
||||
impl<const M: usize> Page<M> {
|
||||
pub fn new(btn_layout: ButtonLayout<&'static str>, btn_actions: ButtonActions) -> Self {
|
||||
pub fn new(
|
||||
btn_layout: ButtonLayout<&'static str>,
|
||||
btn_actions: ButtonActions,
|
||||
initial_text_font: Font,
|
||||
) -> Self {
|
||||
let style = TextStyle::new(
|
||||
Font::NORMAL,
|
||||
initial_text_font,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
theme::FG,
|
||||
@ -200,6 +204,10 @@ impl<const M: usize> Page<M> {
|
||||
self.font(Font::NORMAL).text(text)
|
||||
}
|
||||
|
||||
pub fn text_mono(self, text: StrBuffer) -> Self {
|
||||
self.font(Font::MONO).text(text)
|
||||
}
|
||||
|
||||
pub fn text_bold(self, text: StrBuffer) -> Self {
|
||||
self.font(Font::BOLD).text(text)
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
//! Mostly copy-pasted stuff from ui/component/text,
|
||||
//! but with small modifications.
|
||||
//! It is really mostly changing Op::Text(&'a str) to Op::Text(String<100>),
|
||||
//! having self.ops as Vec<Op, 30> and changes revolving around it.
|
||||
//! Even if some stuff could be reused now, I copy-pasted it anyway, as this
|
||||
//! extension for Icons, Offsets, etc. should no longer live in
|
||||
//! ui/component/text, and so they can be freely removed (as they are here as
|
||||
//! well).
|
||||
//! (support for more Ops like icon drawing or arbitrary offsets)
|
||||
|
||||
use crate::{
|
||||
micropython::buffer::StrBuffer,
|
||||
@ -33,7 +28,7 @@ impl ToDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
/// Operations that can be done on FormattedText.
|
||||
/// Operations that can be done on the screen.
|
||||
#[derive(Clone)]
|
||||
pub enum Op {
|
||||
/// Render text with current color and font.
|
||||
@ -151,26 +146,11 @@ impl TextLayout {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_bounds(mut self, bounds: Rect) -> Self {
|
||||
self.bounds = bounds;
|
||||
self
|
||||
}
|
||||
|
||||
/// Baseline `Point` where we are starting to draw the text.
|
||||
pub fn initial_cursor(&self) -> Point {
|
||||
self.bounds.top_left() + Offset::y(self.style.text_font.text_height() + self.padding_top)
|
||||
}
|
||||
|
||||
/// Trying to fit the content on the current screen.
|
||||
pub fn fit_text(&self, text: &str) -> LayoutFit {
|
||||
self.layout_text(text, &mut self.initial_cursor(), &mut TextNoOp)
|
||||
}
|
||||
|
||||
/// Draw as much text as possible on the current screen.
|
||||
pub fn render_text(&self, text: &str) {
|
||||
self.layout_text(text, &mut self.initial_cursor(), &mut TextRenderer);
|
||||
}
|
||||
|
||||
/// Y coordinate of the bottom of the available space/bounds
|
||||
pub fn bottom_y(&self) -> i16 {
|
||||
(self.bounds.y1 - self.padding_bottom).max(self.bounds.y0)
|
||||
@ -433,7 +413,6 @@ impl TextLayout {
|
||||
|
||||
cursor.x += icon.width() as i16;
|
||||
LayoutFit::Fitting {
|
||||
// TODO: how to handle this? It could collide with "skip_first_n_bytes"
|
||||
processed_chars: 1,
|
||||
height: 0, // it should just draw on one line
|
||||
}
|
||||
@ -467,9 +446,6 @@ impl LayoutFit {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: LayoutSink could support even things like drawing icons
|
||||
// or making custom x or y offsets from any position
|
||||
|
||||
/// Visitor for text segment operations.
|
||||
/// Defines responses for certain kind of events encountered
|
||||
/// when processing the content.
|
||||
|
@ -4,12 +4,10 @@ use crate::ui::{
|
||||
geometry::{Insets, Rect},
|
||||
};
|
||||
|
||||
/// Component for holding another component and displaying
|
||||
/// a title and optionally a subtitle describing that child component.
|
||||
/// Component for holding another component and displaying a title.
|
||||
pub struct Frame<T, U> {
|
||||
area: Rect,
|
||||
title: U,
|
||||
subtitle: Option<U>,
|
||||
content: Child<T>,
|
||||
}
|
||||
|
||||
@ -18,10 +16,9 @@ where
|
||||
T: Component,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
pub fn new(title: U, subtitle: Option<U>, content: T) -> Self {
|
||||
pub fn new(title: U, content: T) -> Self {
|
||||
Self {
|
||||
title,
|
||||
subtitle,
|
||||
area: Rect::zero(),
|
||||
content: Child::new(content),
|
||||
}
|
||||
@ -40,11 +37,10 @@ where
|
||||
type Msg = T::Msg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
// Depending on whether there is subtitle or not
|
||||
let title_space = if self.subtitle.is_some() { 12 } else { 4 };
|
||||
const TITLE_SPACE: i16 = 4;
|
||||
|
||||
let (title_area, content_area) = bounds.split_top(theme::FONT_HEADER.line_height());
|
||||
let content_area = content_area.inset(Insets::top(title_space));
|
||||
let content_area = content_area.inset(Insets::top(TITLE_SPACE));
|
||||
|
||||
self.area = title_area;
|
||||
self.content.place(content_area);
|
||||
@ -56,7 +52,7 @@ where
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
common::paint_header(self.area.top_left(), &self.title, self.subtitle.as_ref());
|
||||
common::paint_header_centered(&self.title, self.area);
|
||||
self.content.paint();
|
||||
}
|
||||
}
|
||||
@ -70,9 +66,6 @@ where
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("Frame");
|
||||
t.title(self.title.as_ref());
|
||||
if let Some(ref subtitle) = self.subtitle {
|
||||
t.title(subtitle.as_ref());
|
||||
}
|
||||
t.field("content", &self.content);
|
||||
t.close();
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||
pub use button_controller::{ButtonController, ButtonControllerMsg};
|
||||
pub use changing_text::ChangingTextLine;
|
||||
pub use choice::{ChoiceFactory, ChoicePage, ChoicePageMsg};
|
||||
pub use choice_item::{ChoiceItem, ChoiceItemAPI, MultilineTextChoiceItem, TextChoiceItem};
|
||||
pub use choice_item::ChoiceItem;
|
||||
pub use dialog::{Dialog, DialogMsg};
|
||||
pub use flow::{Flow, FlowMsg};
|
||||
pub use flow_pages::{FlowPages, Page};
|
||||
|
@ -2,13 +2,16 @@ use crate::{
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||
display::Icon,
|
||||
geometry::Rect,
|
||||
model_tr::theme,
|
||||
util::char_to_string,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
choice_item::BigCharacterChoiceItem, ButtonDetails, ButtonLayout, ChangingTextLine,
|
||||
ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, MultilineTextChoiceItem, TextChoiceItem,
|
||||
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
|
||||
ChoicePageMsg,
|
||||
};
|
||||
use heapless::String;
|
||||
|
||||
@ -44,9 +47,9 @@ const SPECIAL_SYMBOLS: [char; 30] = [
|
||||
'{', '}', ',', '\'', '`', ';', '"', '~', '$', '^', '=',
|
||||
];
|
||||
const MENU_LENGTH: usize = 6;
|
||||
const DEL_INDEX: usize = MENU_LENGTH - 1;
|
||||
const DELETE_INDEX: usize = MENU_LENGTH - 1;
|
||||
const SHOW_INDEX: usize = MENU_LENGTH - 2;
|
||||
const MENU: [&str; MENU_LENGTH] = ["abc", "ABC", "123", "*#_", "SHOW PASS", "DEL LAST CHAR"];
|
||||
const MENU: [&str; MENU_LENGTH] = ["abc", "ABC", "123", "*#_", "SHOW", "DELETE"];
|
||||
|
||||
/// Get a character at a specified index for a specified category.
|
||||
fn get_char(current_category: &ChoiceCategory, index: u8) -> char {
|
||||
@ -104,10 +107,10 @@ impl ChoiceFactoryPassphrase {
|
||||
/// MENU choices with accept and cancel hold-to-confirm side buttons.
|
||||
fn get_menu_item(&self, choice_index: u8) -> ChoiceItem {
|
||||
let choice = MENU[choice_index as usize];
|
||||
let item =
|
||||
MultilineTextChoiceItem::new(String::from(choice), ButtonLayout::default_three_icons())
|
||||
.use_delimiter(' ');
|
||||
let mut menu_item = ChoiceItem::MultilineText(item);
|
||||
let mut menu_item = ChoiceItem::new(
|
||||
String::<50>::from(choice),
|
||||
ButtonLayout::default_three_icons(),
|
||||
);
|
||||
|
||||
// Including accept button on the left and cancel on the very right
|
||||
// TODO: could have some icons instead of the shortcut text
|
||||
@ -121,6 +124,13 @@ impl ChoiceFactoryPassphrase {
|
||||
));
|
||||
}
|
||||
|
||||
// Including icons for some items.
|
||||
if choice_index == DELETE_INDEX as u8 {
|
||||
menu_item = menu_item.with_icon(Icon::new(theme::ICON_DELETE));
|
||||
} else if choice_index == SHOW_INDEX as u8 {
|
||||
menu_item = menu_item.with_icon(Icon::new(theme::ICON_EYE));
|
||||
}
|
||||
|
||||
menu_item
|
||||
}
|
||||
|
||||
@ -128,13 +138,10 @@ impl ChoiceFactoryPassphrase {
|
||||
/// return back
|
||||
fn get_character_item(&self, choice_index: u8) -> ChoiceItem {
|
||||
if is_menu_choice(&self.current_category, choice_index) {
|
||||
let menu_choice =
|
||||
TextChoiceItem::new("MENU", ButtonLayout::three_icons_middle_text("RETURN"));
|
||||
ChoiceItem::Text(menu_choice)
|
||||
ChoiceItem::new("MENU", ButtonLayout::three_icons_middle_text("RETURN"))
|
||||
} else {
|
||||
let ch = get_char(&self.current_category, choice_index);
|
||||
let char_choice = BigCharacterChoiceItem::new(ch, ButtonLayout::default_three_icons());
|
||||
ChoiceItem::BigCharacter(char_choice)
|
||||
ChoiceItem::new(char_to_string::<1>(ch), ButtonLayout::default_three_icons())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -251,7 +258,7 @@ impl Component for PassphraseEntry {
|
||||
match msg {
|
||||
// Going to new category, applying some action or returning the result
|
||||
Some(ChoicePageMsg::Choice(page_counter)) => match page_counter as usize {
|
||||
DEL_INDEX => {
|
||||
DELETE_INDEX => {
|
||||
self.delete_last_digit(ctx);
|
||||
self.update_passphrase_dots(ctx);
|
||||
ctx.request_paint();
|
||||
@ -324,7 +331,7 @@ impl crate::trace::Trace for PassphraseEntry {
|
||||
let current_index = self.choice_page.page_index() as usize;
|
||||
match &self.current_category {
|
||||
ChoiceCategory::Menu => match current_index {
|
||||
DEL_INDEX => ButtonAction::Action("Del last char").string(),
|
||||
DELETE_INDEX => ButtonAction::Action("Del last char").string(),
|
||||
SHOW_INDEX => ButtonAction::Action("Show pass").string(),
|
||||
_ => ButtonAction::select_item(MENU[current_index]),
|
||||
},
|
||||
|
@ -3,13 +3,15 @@ use crate::{
|
||||
trezorhal::random,
|
||||
ui::{
|
||||
component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx},
|
||||
display::Icon,
|
||||
geometry::Rect,
|
||||
model_tr::theme,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
choice_item::BigCharacterChoiceItem, ButtonDetails, ButtonLayout, ChangingTextLine,
|
||||
ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, MultilineTextChoiceItem,
|
||||
ButtonDetails, ButtonLayout, ChangingTextLine, ChoiceFactory, ChoiceItem, ChoicePage,
|
||||
ChoicePageMsg,
|
||||
};
|
||||
use heapless::String;
|
||||
|
||||
@ -22,16 +24,14 @@ const MAX_PIN_LENGTH: usize = 50;
|
||||
const MAX_VISIBLE_DOTS: usize = 18;
|
||||
const MAX_VISIBLE_DIGITS: usize = 18;
|
||||
|
||||
const CHOICE_LENGTH: usize = 14;
|
||||
const EXIT_INDEX: usize = 0;
|
||||
const DELETE_INDEX: usize = 1;
|
||||
const SHOW_INDEX: usize = 2;
|
||||
const PROMPT_INDEX: usize = 3;
|
||||
const CHOICE_LENGTH: usize = 13;
|
||||
const DELETE_INDEX: usize = 0;
|
||||
const SHOW_INDEX: usize = 1;
|
||||
const PROMPT_INDEX: usize = 2;
|
||||
const CHOICES: [&str; CHOICE_LENGTH] = [
|
||||
"EXIT",
|
||||
"DELETE",
|
||||
"SHOW PIN",
|
||||
"PLACEHOLDER FOR THE PROMPT",
|
||||
"SHOW",
|
||||
"ENTER PIN",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
@ -56,32 +56,23 @@ impl ChoiceFactoryPIN {
|
||||
|
||||
impl ChoiceFactory for ChoiceFactoryPIN {
|
||||
fn get(&self, choice_index: u8) -> ChoiceItem {
|
||||
let choice = CHOICES[choice_index as usize];
|
||||
let choice_str = CHOICES[choice_index as usize];
|
||||
|
||||
// Depending on whether it is a digit (one character) or a text.
|
||||
// Digits are BIG, the rest is multiline.
|
||||
let mut choice_item = if choice.len() == 1 {
|
||||
let item =
|
||||
BigCharacterChoiceItem::from_str(choice, ButtonLayout::default_three_icons());
|
||||
ChoiceItem::BigCharacter(item)
|
||||
} else {
|
||||
let item = MultilineTextChoiceItem::new(
|
||||
String::from(choice),
|
||||
ButtonLayout::default_three_icons(),
|
||||
)
|
||||
.use_delimiter(' ');
|
||||
ChoiceItem::MultilineText(item)
|
||||
};
|
||||
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
|
||||
|
||||
// Action buttons have different middle button text
|
||||
if [EXIT_INDEX, DELETE_INDEX, SHOW_INDEX, PROMPT_INDEX].contains(&(choice_index as usize)) {
|
||||
if [DELETE_INDEX, SHOW_INDEX, PROMPT_INDEX].contains(&(choice_index as usize)) {
|
||||
let confirm_btn = ButtonDetails::armed_text("CONFIRM");
|
||||
choice_item.set_middle_btn(Some(confirm_btn));
|
||||
}
|
||||
|
||||
// Changing the prompt text for the wanted one
|
||||
if choice_index == PROMPT_INDEX as u8 {
|
||||
choice_item.set_text(String::from(self.prompt.as_ref()));
|
||||
// Adding icons for appropriate items
|
||||
if choice_index == DELETE_INDEX as u8 {
|
||||
choice_item = choice_item.with_icon(Icon::new(theme::ICON_DELETE));
|
||||
} else if choice_index == SHOW_INDEX as u8 {
|
||||
choice_item = choice_item.with_icon(Icon::new(theme::ICON_EYE));
|
||||
} else if choice_index == PROMPT_INDEX as u8 {
|
||||
choice_item = choice_item.with_icon(Icon::new(theme::ICON_TICK));
|
||||
}
|
||||
|
||||
choice_item
|
||||
@ -106,7 +97,7 @@ impl PinEntry {
|
||||
|
||||
Self {
|
||||
choice_page: ChoicePage::new(choices)
|
||||
.with_initial_page_counter(3)
|
||||
.with_initial_page_counter(PROMPT_INDEX as u8)
|
||||
.with_carousel(),
|
||||
pin_dots: Child::new(ChangingTextLine::center_mono(String::new())),
|
||||
show_real_pin: false,
|
||||
@ -177,7 +168,6 @@ impl Component for PinEntry {
|
||||
if let Some(ChoicePageMsg::Choice(page_counter)) = msg {
|
||||
// Performing action under specific index or appending new digit
|
||||
match page_counter as usize {
|
||||
EXIT_INDEX => return Some(PinEntryMsg::Cancelled),
|
||||
DELETE_INDEX => {
|
||||
self.delete_last_digit(ctx);
|
||||
self.update_pin_dots(ctx);
|
||||
@ -194,8 +184,10 @@ impl Component for PinEntry {
|
||||
self.append_new_digit(ctx, page_counter);
|
||||
self.update_pin_dots(ctx);
|
||||
// Choosing any random digit to be shown next
|
||||
let new_page_counter =
|
||||
random::uniform_between(4, (CHOICE_LENGTH - 1) as u32);
|
||||
let new_page_counter = random::uniform_between(
|
||||
PROMPT_INDEX as u32 + 1,
|
||||
(CHOICE_LENGTH - 1) as u32,
|
||||
);
|
||||
self.choice_page
|
||||
.set_page_counter(ctx, new_page_counter as u8);
|
||||
ctx.request_paint();
|
||||
@ -224,7 +216,6 @@ impl crate::trace::Trace for PinEntry {
|
||||
ButtonPos::Middle => {
|
||||
let current_index = self.choice_page.page_index() as usize;
|
||||
match current_index {
|
||||
EXIT_INDEX => ButtonAction::Cancel.string(),
|
||||
DELETE_INDEX => ButtonAction::Action("Delete last digit").string(),
|
||||
SHOW_INDEX => ButtonAction::Action("Show PIN").string(),
|
||||
PROMPT_INDEX => ButtonAction::Confirm.string(),
|
||||
|
@ -3,7 +3,7 @@ use crate::ui::{
|
||||
geometry::Rect,
|
||||
};
|
||||
|
||||
use super::{ButtonLayout, ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg, TextChoiceItem};
|
||||
use super::{ButtonLayout, ChoiceFactory, ChoiceItem, ChoicePage, ChoicePageMsg};
|
||||
use heapless::{String, Vec};
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
@ -32,8 +32,7 @@ where
|
||||
{
|
||||
fn get(&self, choice_index: u8) -> ChoiceItem {
|
||||
let text = &self.choices[choice_index as usize];
|
||||
let text_item = TextChoiceItem::new(text, ButtonLayout::default_three_icons());
|
||||
let mut choice_item = ChoiceItem::Text(text_item);
|
||||
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
|
||||
|
||||
// Disabling prev/next buttons for the first/last choice.
|
||||
if choice_index == 0 {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::ui::geometry::{Offset, Point, Rect};
|
||||
|
||||
pub const WIDTH: i16 = 128;
|
||||
pub const HEIGHT: i16 = 128;
|
||||
pub const HEIGHT: i16 = 64;
|
||||
pub const LINE_SPACE: i16 = 1;
|
||||
pub const FONT_BPP: i16 = 1;
|
||||
|
||||
|
@ -21,9 +21,11 @@ use crate::{
|
||||
text::paragraphs::{Paragraph, Paragraphs},
|
||||
FormattedText,
|
||||
},
|
||||
display::Font,
|
||||
layout::{
|
||||
obj::{ComponentMsgObj, LayoutObj},
|
||||
result::{CANCELLED, CONFIRMED, INFO}, util::iter_into_vec,
|
||||
result::{CANCELLED, CONFIRMED, INFO},
|
||||
util::iter_into_vec,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -160,9 +162,8 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
title,
|
||||
None,
|
||||
ButtonPage::new_str_buf(
|
||||
FormattedText::new(theme::TEXT_NORMAL, theme::FORMATTED, format)
|
||||
FormattedText::new(theme::TEXT_MONO, theme::FORMATTED, format)
|
||||
.with("action", action.unwrap_or_default())
|
||||
.with("description", description.unwrap_or_default()),
|
||||
theme::BG,
|
||||
@ -186,10 +187,9 @@ extern "C" fn new_confirm_text(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
title,
|
||||
None,
|
||||
ButtonPage::new_str(
|
||||
Paragraphs::new([
|
||||
Paragraph::new(&theme::TEXT_NORMAL, description.unwrap_or_default()),
|
||||
Paragraph::new(&theme::TEXT_MONO, description.unwrap_or_default()),
|
||||
Paragraph::new(&theme::TEXT_BOLD, data),
|
||||
]),
|
||||
theme::BG,
|
||||
@ -222,7 +222,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
|
||||
Some(ButtonDetails::text("CONTINUE")),
|
||||
);
|
||||
let btn_actions = ButtonActions::cancel_next();
|
||||
Page::<20>::new(btn_layout, btn_actions).icon_label_text(
|
||||
Page::<20>::new(btn_layout, btn_actions, Font::NORMAL).icon_label_text(
|
||||
theme::ICON_USER,
|
||||
"Recipient".into(),
|
||||
address.clone(),
|
||||
@ -239,7 +239,7 @@ extern "C" fn confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map)
|
||||
),
|
||||
);
|
||||
let btn_actions = ButtonActions::cancel_confirm();
|
||||
Page::<20>::new(btn_layout, btn_actions)
|
||||
Page::<20>::new(btn_layout, btn_actions, Font::NORMAL)
|
||||
.icon_label_text(
|
||||
theme::ICON_USER,
|
||||
"Recipient".into(),
|
||||
@ -281,7 +281,7 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
|
||||
);
|
||||
let btn_actions = ButtonActions::cancel_confirm();
|
||||
|
||||
let mut flow_page = Page::<25>::new(btn_layout, btn_actions)
|
||||
let mut flow_page = Page::<25>::new(btn_layout, btn_actions, Font::NORMAL)
|
||||
.icon_label_text(theme::ICON_PARAM, total_label.clone(), total_amount.clone())
|
||||
.newline()
|
||||
.icon_label_text(theme::ICON_PARAM, fee_label.clone(), fee_amount.clone());
|
||||
@ -305,6 +305,8 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
|
||||
|
||||
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = |_args: &[Obj], _kwargs: &Map| {
|
||||
const PAGE_COUNT: u8 = 7;
|
||||
|
||||
let get_page = |page_index| {
|
||||
// Lazy-loaded list of screens to show, with custom content,
|
||||
// buttons and actions triggered by these buttons.
|
||||
@ -314,29 +316,19 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
||||
let screen = match page_index {
|
||||
// title, text, btn_layout, btn_actions
|
||||
0 => (
|
||||
"Hello!",
|
||||
"Welcome to Trezor.\n\n\nPress right to continue.",
|
||||
"HELLO",
|
||||
"Welcome to Trezor.\nPress right to continue.",
|
||||
ButtonLayout::cancel_and_arrow(),
|
||||
ButtonActions::last_next(),
|
||||
),
|
||||
1 => (
|
||||
"Basics",
|
||||
"Use Trezor by clicking left & right.\nPress right to continue.",
|
||||
"",
|
||||
"Use Trezor by clicking left & right.\n\nContinue right.",
|
||||
ButtonLayout::left_right_arrows(),
|
||||
ButtonActions::prev_next(),
|
||||
),
|
||||
2 => (
|
||||
"Confirm",
|
||||
"Press both left & right at the same time to confirm.",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
Some(ButtonDetails::armed_text("CONFIRM")),
|
||||
None,
|
||||
),
|
||||
ButtonActions::prev_next_with_middle(),
|
||||
),
|
||||
3 => (
|
||||
"Hold to confirm",
|
||||
"HOLD TO CONFIRM",
|
||||
"Press & hold right to approve important operations.",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
@ -348,29 +340,27 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
||||
),
|
||||
ButtonActions::prev_next(),
|
||||
),
|
||||
// TODO: merge these two scrolls into one, with using a scrollbar
|
||||
4 => (
|
||||
"Screen scroll",
|
||||
"Press right to scroll down to read all content when text doesn't...",
|
||||
3 => (
|
||||
"SCREEN SCROLL",
|
||||
"Press right to scroll down to read all content when text\ndoesn't fit on one screen. Press left to scroll up.",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
None,
|
||||
Some(ButtonDetails::down_arrow_icon_wide()),
|
||||
),
|
||||
Some(ButtonDetails::text("GOT IT")), ),
|
||||
ButtonActions::prev_next(),
|
||||
),
|
||||
4 => (
|
||||
"CONFIRM",
|
||||
"Press both left & right at the same time to confirm.",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
Some(ButtonDetails::armed_text("CONFIRM")),
|
||||
None,
|
||||
),
|
||||
ButtonActions::prev_next_with_middle(),
|
||||
),
|
||||
5 => (
|
||||
"Screen scroll",
|
||||
"fit on one screen. Press left to scroll up.",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::up_arrow_icon_wide()),
|
||||
None,
|
||||
Some(ButtonDetails::text("CONFIRM")),
|
||||
),
|
||||
ButtonActions::prev_next(),
|
||||
),
|
||||
6 => (
|
||||
"Congrats!",
|
||||
"CONGRATS!",
|
||||
"You're ready to use Trezor.",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::text("AGAIN")),
|
||||
@ -379,27 +369,30 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
|
||||
),
|
||||
ButtonActions::beginning_confirm(),
|
||||
),
|
||||
7 => (
|
||||
"Skip tutorial?",
|
||||
6 => (
|
||||
"SKIP TUTORIAL",
|
||||
"Sure you want to skip the tutorial?",
|
||||
ButtonLayout::new(
|
||||
Some(ButtonDetails::left_arrow_icon()),
|
||||
None,
|
||||
Some(ButtonDetails::text("CONFIRM")),
|
||||
Some(ButtonDetails::text("SKIP")),
|
||||
),
|
||||
ButtonActions::beginning_cancel(),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Page::<10>::new(screen.2.clone(), screen.3.clone())
|
||||
.text_bold(screen.0.into())
|
||||
.newline()
|
||||
.newline_half()
|
||||
.text_normal(screen.1.into())
|
||||
let mut page = Page::<10>::new(screen.2.clone(), screen.3.clone(), Font::BOLD);
|
||||
|
||||
// Add title if present
|
||||
if !screen.0.is_empty() {
|
||||
page = page.text_bold(screen.0.into()).newline().newline_half()
|
||||
}
|
||||
page = page.text_mono(screen.1.into());
|
||||
page
|
||||
};
|
||||
|
||||
let pages = FlowPages::new(get_page, 8);
|
||||
let pages = FlowPages::new(get_page, PAGE_COUNT);
|
||||
|
||||
let obj = LayoutObj::new(Flow::new(pages).into_child())?;
|
||||
Ok(obj.into())
|
||||
@ -418,7 +411,7 @@ extern "C" fn pin_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
// as in the design.
|
||||
0 => (
|
||||
"PIN settings".into(),
|
||||
"PIN should\ncontain at\nleast four\ndigits",
|
||||
"PIN should contain at least 6 digits",
|
||||
ButtonLayout::cancel_and_text("GOT IT"),
|
||||
ButtonActions::cancel_next(),
|
||||
),
|
||||
@ -434,11 +427,11 @@ extern "C" fn pin_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Page::<10>::new(screen.2.clone(), screen.3.clone())
|
||||
Page::<10>::new(screen.2.clone(), screen.3.clone(), Font::BOLD)
|
||||
.text_bold(screen.0)
|
||||
.newline()
|
||||
.newline_half()
|
||||
.text_normal(screen.1.into())
|
||||
.text_mono(screen.1.into())
|
||||
};
|
||||
let pages = FlowPages::new(get_page, 2);
|
||||
|
||||
@ -464,7 +457,7 @@ extern "C" fn request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
|
||||
extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = |_args: &[Obj], kwargs: &Map| {
|
||||
let share_words_obj: Obj = kwargs.get(Qstr::MP_QSTR_share_words)?;
|
||||
let title = "Recovery seed";
|
||||
let title = "RECOVERY SEED";
|
||||
|
||||
// Parsing the list of share words.
|
||||
// Assume there is always up to 24 words in the newly generated seed
|
||||
@ -519,13 +512,8 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
title,
|
||||
None,
|
||||
ButtonPage::new_str(
|
||||
Paragraphs::new(
|
||||
[
|
||||
Paragraph::new(&theme::TEXT_BOLD, text_to_show)
|
||||
]
|
||||
),
|
||||
Paragraphs::new([Paragraph::new(&theme::TEXT_BOLD, text_to_show)]),
|
||||
theme::BG,
|
||||
)
|
||||
.with_cancel_btn(cancel_btn)
|
||||
@ -539,15 +527,10 @@ extern "C" fn show_share_words(n_args: usize, args: *const Obj, kwargs: *mut Map
|
||||
extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||
let words_iterable: Obj = kwargs.get(Qstr::MP_QSTR_words)?;
|
||||
let words: Vec<StrBuffer, 3> = iter_into_vec(words_iterable)?;
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
title,
|
||||
Some(description),
|
||||
SimpleChoice::new(words).into_child(),
|
||||
))?;
|
||||
let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(words).into_child()))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -556,15 +539,10 @@ extern "C" fn select_word(n_args: usize, args: *const Obj, kwargs: *mut Map) ->
|
||||
extern "C" fn request_word_count(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||
let block = |_args: &[Obj], kwargs: &Map| {
|
||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||
let text: StrBuffer = kwargs.get(Qstr::MP_QSTR_text)?.try_into()?;
|
||||
|
||||
let choices: Vec<&str, 5> = ["12", "18", "20", "24", "33"].into_iter().collect();
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
title,
|
||||
Some(text),
|
||||
SimpleChoice::new(choices).into_child(),
|
||||
))?;
|
||||
let obj = LayoutObj::new(Frame::new(title, SimpleChoice::new(choices).into_child()))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -574,7 +552,7 @@ extern "C" fn request_word_bip39(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
let block = |_args: &[Obj], kwargs: &Map| {
|
||||
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(prompt, None, Bip39Entry::new().into_child()))?;
|
||||
let obj = LayoutObj::new(Frame::new(prompt, Bip39Entry::new().into_child()))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -585,11 +563,7 @@ extern "C" fn request_passphrase(n_args: usize, args: *const Obj, kwargs: *mut M
|
||||
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||
let _max_len: u8 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
|
||||
|
||||
let obj = LayoutObj::new(Frame::new(
|
||||
prompt,
|
||||
None,
|
||||
PassphraseEntry::new().into_child(),
|
||||
))?;
|
||||
let obj = LayoutObj::new(Frame::new(prompt, PassphraseEntry::new().into_child()))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
@ -678,7 +652,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def select_word(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// description: str,
|
||||
/// words: Iterable[str],
|
||||
/// ) -> int:
|
||||
/// """Select mnemonic word from three possibilities - seed check after backup. The
|
||||
@ -688,7 +661,6 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// def request_word_count(
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// text: str,
|
||||
/// ) -> str: # TODO: make it return int
|
||||
/// """Get word count for recovery."""
|
||||
Qstr::MP_QSTR_request_word_count => obj_fn_kw!(0, request_word_count).as_obj(),
|
||||
|
BIN
core/embed/rust/src/ui/model_tr/res/delete.toif
Normal file
BIN
core/embed/rust/src/ui/model_tr/res/delete.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tr/res/eye.toif
Normal file
BIN
core/embed/rust/src/ui/model_tr/res/eye.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tr/res/tick.toif
Normal file
BIN
core/embed/rust/src/ui/model_tr/res/tick.toif
Normal file
Binary file not shown.
@ -9,7 +9,8 @@ pub const BG: Color = Color::black(); // Default background color.
|
||||
|
||||
// Font constants.
|
||||
pub const FONT_BUTTON: Font = Font::MONO;
|
||||
pub const FONT_HEADER: Font = Font::MONO;
|
||||
pub const FONT_HEADER: Font = Font::BOLD;
|
||||
pub const FONT_CHOICE_ITEMS: Font = Font::NORMAL;
|
||||
|
||||
// Text constants.
|
||||
pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, FG, BG, FG, FG);
|
||||
@ -25,17 +26,6 @@ pub const FORMATTED: FormattedFonts = FormattedFonts {
|
||||
};
|
||||
|
||||
// Icons with their names for debugging purposes
|
||||
pub const ICON_SUCCESS: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/success.toif"), "success");
|
||||
pub const ICON_FAIL: IconAndName = IconAndName::new(include_res!("model_tr/res/fail.toif"), "fail");
|
||||
pub const ICON_CANCEL_OUTLINE: IconAndName = IconAndName::new(
|
||||
include_res!("model_tr/res/cancel_for_outline.toif"),
|
||||
"cancel_outline",
|
||||
); // 8*8
|
||||
pub const ICON_CANCEL: IconAndName = IconAndName::new(
|
||||
include_res!("model_tr/res/cancel_no_outline.toif"),
|
||||
"cancel",
|
||||
);
|
||||
pub const ICON_ARM_LEFT: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/arm_left.toif"), "arm_left"); // 6*10
|
||||
pub const ICON_ARM_RIGHT: IconAndName =
|
||||
@ -48,12 +38,27 @@ pub const ICON_ARROW_UP: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/arrow_up.toif"), "arrow_up"); // 10*6
|
||||
pub const ICON_ARROW_DOWN: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/arrow_down.toif"), "arrow_down"); // 10*6
|
||||
pub const ICON_BIN: IconAndName = IconAndName::new(include_res!("model_tr/res/bin.toif"), "bin"); // 10*10
|
||||
pub const ICON_AMOUNT: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/amount.toif"), "amount"); // 10*10
|
||||
pub const ICON_BIN: IconAndName = IconAndName::new(include_res!("model_tr/res/bin.toif"), "bin"); // 10*10
|
||||
pub const ICON_CANCEL_OUTLINE: IconAndName = IconAndName::new(
|
||||
include_res!("model_tr/res/cancel_for_outline.toif"),
|
||||
"cancel_outline",
|
||||
); // 8*8
|
||||
pub const ICON_CANCEL: IconAndName = IconAndName::new(
|
||||
include_res!("model_tr/res/cancel_no_outline.toif"),
|
||||
"cancel",
|
||||
); // 8*8
|
||||
pub const ICON_DELETE: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/delete.toif"), "delete"); // 12*8
|
||||
pub const ICON_EYE: IconAndName = IconAndName::new(include_res!("model_tr/res/eye.toif"), "eye"); // 12*6
|
||||
pub const ICON_FAIL: IconAndName = IconAndName::new(include_res!("model_tr/res/fail.toif"), "fail");
|
||||
pub const ICON_LOCK: IconAndName = IconAndName::new(include_res!("model_tr/res/lock.toif"), "lock"); // 10*10
|
||||
pub const ICON_PARAM: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/param.toif"), "param"); // 10*10
|
||||
pub const ICON_SUCCESS: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/success.toif"), "success");
|
||||
pub const ICON_TICK: IconAndName = IconAndName::new(include_res!("model_tr/res/tick.toif"), "tick"); // 10*10
|
||||
pub const ICON_USER: IconAndName = IconAndName::new(include_res!("model_tr/res/user.toif"), "user"); // 10*10
|
||||
pub const ICON_WALLET: IconAndName =
|
||||
IconAndName::new(include_res!("model_tr/res/wallet.toif"), "wallet"); // 10*10
|
||||
|
@ -109,7 +109,6 @@ def show_share_words(
|
||||
def select_word(
|
||||
*,
|
||||
title: str,
|
||||
description: str,
|
||||
words: Iterable[str],
|
||||
) -> int:
|
||||
"""Select mnemonic word from three possibilities - seed check after backup. The
|
||||
@ -120,7 +119,6 @@ def select_word(
|
||||
def request_word_count(
|
||||
*,
|
||||
title: str,
|
||||
text: str,
|
||||
) -> str: # TODO: make it return int
|
||||
"""Get word count for recovery."""
|
||||
|
||||
|
@ -65,12 +65,10 @@ class HomescreenBase(ui.Layout):
|
||||
return storage.device.get_homescreen() or res.load(
|
||||
"apps/homescreen/res/bg.toif"
|
||||
)
|
||||
elif utils.MODEL in ("R",):
|
||||
elif utils.MODEL in ("1", "R"):
|
||||
# TODO: make it possible to change
|
||||
# TODO: make it a requirement of 128x64 px
|
||||
# TODO: make it a requirement of XxX px
|
||||
# TODO: support it for ui.display.avatar, not only ui.display.icon
|
||||
return res.load("trezor/res/model_r/homescreen.toif") # 128*64 px
|
||||
elif utils.MODEL in ("1",):
|
||||
return res.load("trezor/res/homescreen_model_1.toif") # 64x36 px
|
||||
else:
|
||||
raise Exception("Unknown model")
|
||||
|
@ -21,17 +21,17 @@ class Homescreen(HomescreenBase):
|
||||
# the icon more on the top.
|
||||
# Otherwise just showing the uppercase label in monospace.
|
||||
if not storage.device.is_initialized():
|
||||
ui.display.icon(0, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
|
||||
ui.display.icon(32, 5, self.get_avatar(), ui.style.FG, ui.style.BG)
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 98, "Go to", ui.BOLD, ui.FG, ui.BG
|
||||
ui.WIDTH // 2, 52, "Go to", ui.BOLD, ui.FG, ui.BG
|
||||
)
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 112, "trezor.io/start", ui.BOLD, ui.FG, ui.BG
|
||||
ui.WIDTH // 2, 60, "trezor.io/start", ui.BOLD, ui.FG, ui.BG
|
||||
)
|
||||
else:
|
||||
ui.display.icon(0, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
|
||||
ui.display.icon(32, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 112, self.label.upper(), ui.MONO, ui.FG, ui.BG
|
||||
ui.WIDTH // 2, 60, self.label.upper(), ui.MONO, ui.FG, ui.BG
|
||||
)
|
||||
|
||||
|
||||
@ -57,20 +57,20 @@ class Lockscreen(HomescreenBase):
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 9, self.label.upper(), ui.MONO, ui.FG, ui.BG
|
||||
)
|
||||
ui.display.icon(0, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
|
||||
ui.display.icon(32, 11, self.get_avatar(), ui.style.FG, ui.style.BG)
|
||||
|
||||
# Lock icon placement depends on the lock_label text
|
||||
lock_icon = ui.res.load("trezor/res/model_r/lock.toif")
|
||||
if self.lock_label == "Not connected":
|
||||
ui.display.icon(13, 90, lock_icon, ui.style.FG, ui.style.BG)
|
||||
ui.display.icon(13, 45, lock_icon, ui.style.FG, ui.style.BG)
|
||||
else:
|
||||
ui.display.icon(38, 90, lock_icon, ui.style.FG, ui.style.BG)
|
||||
ui.display.icon(38, 45, lock_icon, ui.style.FG, ui.style.BG)
|
||||
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2 + 10, 100, self.lock_label.upper(), ui.NORMAL, ui.FG, ui.BG
|
||||
ui.WIDTH // 2 + 10, 52, self.lock_label.upper(), ui.NORMAL, ui.FG, ui.BG
|
||||
)
|
||||
ui.display.text_center(
|
||||
ui.WIDTH // 2, 115, self.tap_label.upper(), ui.MONO, ui.FG, ui.BG
|
||||
ui.WIDTH // 2, 60, self.tap_label.upper(), ui.MONO, ui.FG, ui.BG
|
||||
)
|
||||
|
||||
def on_button_released(self, _x: int) -> None:
|
||||
|
@ -406,7 +406,7 @@ async def _placeholder_confirm(
|
||||
confirm_text(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=data,
|
||||
description=description,
|
||||
br_code=br_code,
|
||||
@ -429,7 +429,7 @@ async def get_bool(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.confirm_action(
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
action=data,
|
||||
description=description,
|
||||
verb=verb,
|
||||
@ -501,7 +501,7 @@ async def confirm_action(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.confirm_action(
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
action=action,
|
||||
description=description,
|
||||
verb=verb,
|
||||
@ -565,7 +565,7 @@ async def confirm_reset_device(
|
||||
return await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="recover_device" if recovery else "setup_device",
|
||||
title="Recovery mode" if recovery else "Create new wallet",
|
||||
title="RECOVERY MODE" if recovery else "CREATE NEW WALLET",
|
||||
data="By continuing you agree to trezor.io/tos",
|
||||
description=prompt,
|
||||
br_code=ButtonRequestType.ProtectCall
|
||||
@ -578,10 +578,10 @@ async def confirm_reset_device(
|
||||
async def confirm_backup(ctx: wire.GenericContext) -> bool:
|
||||
if await get_bool(
|
||||
ctx=ctx,
|
||||
title="Success",
|
||||
data="\nNew wallet created successfully!\n\n\nYou should back up your new wallet right now.",
|
||||
verb="Back up",
|
||||
verb_cancel="Skip",
|
||||
title="SUCCESS",
|
||||
data="New wallet created successfully!\nYou should back up your new wallet right now.",
|
||||
verb="BACK UP",
|
||||
verb_cancel="SKIP",
|
||||
br_type="backup_device",
|
||||
br_code=ButtonRequestType.ResetDevice,
|
||||
):
|
||||
@ -589,11 +589,11 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool:
|
||||
|
||||
confirmed = await get_bool(
|
||||
ctx=ctx,
|
||||
title="Warning",
|
||||
title="WARNING",
|
||||
data="Are you sure you want to skip the backup?\n\n",
|
||||
description="You can back up your Trezor once, at any time.",
|
||||
verb="Back up",
|
||||
verb_cancel="Skip",
|
||||
verb="BACK UP",
|
||||
verb_cancel="SKIP",
|
||||
br_type="backup_device",
|
||||
br_code=ButtonRequestType.ResetDevice,
|
||||
)
|
||||
@ -606,7 +606,7 @@ async def confirm_path_warning(
|
||||
return await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="path_warning",
|
||||
title="Confirm path",
|
||||
title="CONFIRM PATH",
|
||||
data=f"{path_type}\n{path} is unknown.\nAre you sure?",
|
||||
description="",
|
||||
br_code=ButtonRequestType.UnknownDerivationPath,
|
||||
@ -619,7 +619,7 @@ async def show_xpub(
|
||||
return await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="show_xpub",
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=xpub,
|
||||
description="",
|
||||
br_code=ButtonRequestType.PublicKey,
|
||||
@ -648,7 +648,7 @@ async def show_address(
|
||||
return await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="show_address",
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=text,
|
||||
description="",
|
||||
br_code=ButtonRequestType.Address,
|
||||
@ -661,7 +661,7 @@ def show_pubkey(
|
||||
return confirm_blob(
|
||||
ctx,
|
||||
br_type="show_pubkey",
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=pubkey,
|
||||
br_code=ButtonRequestType.PublicKey,
|
||||
)
|
||||
@ -684,7 +684,7 @@ async def _show_modal(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
br_code=br_code,
|
||||
title=header,
|
||||
title=header.upper(),
|
||||
action=subheader,
|
||||
description=content,
|
||||
verb=button_confirm,
|
||||
@ -832,7 +832,7 @@ async def confirm_payment_request(
|
||||
return await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="confirm_payment_request",
|
||||
title="Confirm sending",
|
||||
title="CONFIRM SENDING",
|
||||
data=f"{amount} to\n{recipient_name}\n{memos_str}",
|
||||
description="",
|
||||
br_code=ButtonRequestType.ConfirmOutput,
|
||||
@ -853,7 +853,7 @@ async def should_show_more(
|
||||
) -> bool:
|
||||
return await get_bool(
|
||||
ctx=ctx,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=button_text,
|
||||
br_type=br_type,
|
||||
br_code=br_code,
|
||||
@ -875,7 +875,7 @@ async def confirm_blob(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=str(data),
|
||||
description=description,
|
||||
br_code=br_code,
|
||||
@ -895,7 +895,7 @@ async def confirm_address(
|
||||
return confirm_blob(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=address,
|
||||
description=description,
|
||||
br_code=br_code,
|
||||
@ -916,7 +916,7 @@ async def confirm_text(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.confirm_text(
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=data,
|
||||
description=description,
|
||||
)
|
||||
@ -940,7 +940,7 @@ def confirm_amount(
|
||||
return _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=amount,
|
||||
description=description,
|
||||
br_code=br_code,
|
||||
@ -960,7 +960,7 @@ async def confirm_properties(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data="\n\n".join(f"{name or ''}\n{value or ''}" for name, value in props),
|
||||
description="",
|
||||
br_code=br_code,
|
||||
@ -984,7 +984,7 @@ async def confirm_total(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.confirm_total_r(
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
total_amount=total_amount,
|
||||
fee_amount=fee_amount,
|
||||
fee_rate_amount=fee_rate_amount,
|
||||
@ -1004,7 +1004,7 @@ async def confirm_joint_total(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="confirm_joint_total",
|
||||
title="Joint transaction",
|
||||
title="JOINT TRANSACTION",
|
||||
data=f"You are contributing:\n{spending_amount}\nto the total amount:\n{total_amount}",
|
||||
description="",
|
||||
br_code=ButtonRequestType.SignTx,
|
||||
@ -1032,7 +1032,7 @@ async def confirm_metadata(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=title,
|
||||
title=title.upper(),
|
||||
data=text,
|
||||
description="",
|
||||
br_code=br_code,
|
||||
@ -1045,7 +1045,7 @@ async def confirm_replacement(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="confirm_replacement",
|
||||
title=description,
|
||||
title=description.upper(),
|
||||
data=f"Confirm transaction ID:\n{txid}",
|
||||
description="",
|
||||
br_code=ButtonRequestType.SignTx,
|
||||
@ -1069,7 +1069,7 @@ async def confirm_modify_output(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="modify_output",
|
||||
title="Modify amount",
|
||||
title="MODIFY AMOUNT",
|
||||
data=text,
|
||||
description="",
|
||||
br_code=ButtonRequestType.ConfirmOutput,
|
||||
@ -1099,7 +1099,7 @@ async def confirm_modify_fee(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="modify_fee",
|
||||
title="Modify fee",
|
||||
title="MODIFY FEE",
|
||||
data=text,
|
||||
description="",
|
||||
br_code=ButtonRequestType.SignTx,
|
||||
@ -1112,7 +1112,7 @@ async def confirm_coinjoin(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="coinjoin_final",
|
||||
title="Authorize CoinJoin",
|
||||
title="AUTHORIZE COINJOIN",
|
||||
data=f"Maximum rounds: {max_rounds}\n\nMaximum mining fee:\n{max_fee_per_vbyte} sats/vbyte",
|
||||
description="",
|
||||
br_code=ButtonRequestType.Other,
|
||||
@ -1131,7 +1131,7 @@ async def confirm_sign_identity(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type="confirm_sign_identity",
|
||||
title=f"Sign {proto}",
|
||||
title=f"Sign {proto}".upper(),
|
||||
data=text,
|
||||
description="",
|
||||
br_code=ButtonRequestType.Other,
|
||||
@ -1151,7 +1151,7 @@ async def confirm_signverify(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=header,
|
||||
title=header.upper(),
|
||||
data=f"Confirm address:\n{address}",
|
||||
description="",
|
||||
br_code=ButtonRequestType.Other,
|
||||
@ -1160,7 +1160,7 @@ async def confirm_signverify(
|
||||
await _placeholder_confirm(
|
||||
ctx=ctx,
|
||||
br_type=br_type,
|
||||
title=header,
|
||||
title=header.upper(),
|
||||
data=f"Confirm message:\n{message}",
|
||||
description="",
|
||||
br_code=ButtonRequestType.Other,
|
||||
|
@ -14,19 +14,11 @@ if TYPE_CHECKING:
|
||||
|
||||
async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
||||
await button_request(ctx, "word_count", code=ButtonRequestType.MnemonicWordCount)
|
||||
|
||||
if dry_run:
|
||||
title = "Seed check"
|
||||
else:
|
||||
title = "Recovery mode"
|
||||
text = "Number of words?"
|
||||
|
||||
count = await interact(
|
||||
ctx,
|
||||
RustLayout(
|
||||
trezorui2.request_word_count(
|
||||
title=title,
|
||||
text=text,
|
||||
title="NUMBER OF WORDS",
|
||||
)
|
||||
),
|
||||
br_type="request_word_count",
|
||||
@ -39,7 +31,7 @@ async def request_word_count(ctx: wire.GenericContext, dry_run: bool) -> int:
|
||||
async def request_word(
|
||||
ctx: wire.GenericContext, word_index: int, word_count: int, is_slip39: bool
|
||||
) -> str:
|
||||
prompt = f"Choose word {word_index + 1}/{word_count}"
|
||||
prompt = f"WORD {word_index + 1} OF {word_count}"
|
||||
|
||||
if is_slip39:
|
||||
raise NotImplementedError
|
||||
@ -79,11 +71,13 @@ async def continue_recovery(
|
||||
text: str,
|
||||
subtext: str | None,
|
||||
info_func: Callable | None,
|
||||
dry_run: bool,
|
||||
) -> bool:
|
||||
return await get_bool(
|
||||
ctx=ctx,
|
||||
title="Recovery",
|
||||
title="START RECOVERY",
|
||||
data=f"{text}\n\n{subtext or ''}",
|
||||
verb="START",
|
||||
br_type="recovery",
|
||||
br_code=ButtonRequestType.RecoveryHomepage,
|
||||
)
|
||||
|
@ -39,19 +39,14 @@ async def select_word(
|
||||
count: int,
|
||||
group_index: int | None = None,
|
||||
) -> str:
|
||||
# TODO: it might not always be 3 words, it can happen it will be only two,
|
||||
# but the probability is very small - 4 words containing two items two times
|
||||
# (or one in "all all" seed)
|
||||
assert len(words) == 3
|
||||
if share_index is None:
|
||||
title: str = "CHECK SEED"
|
||||
elif group_index is None:
|
||||
title = f"CHECK SHARE #{share_index + 1}"
|
||||
else:
|
||||
title = f"CHECK G{group_index + 1} - SHARE {share_index + 1}"
|
||||
|
||||
result = await ctx.wait(
|
||||
RustLayout(
|
||||
trezorui2.select_word(
|
||||
title=title,
|
||||
description=f"Select word {checked_index + 1}/{count}",
|
||||
title=f"SELECT WORD {checked_index + 1}",
|
||||
words=(words[0].upper(), words[1].upper(), words[2].upper()),
|
||||
)
|
||||
)
|
||||
@ -91,19 +86,11 @@ async def slip39_advanced_prompt_group_threshold(
|
||||
|
||||
|
||||
async def show_warning_backup(ctx: wire.GenericContext, slip39: bool) -> None:
|
||||
if slip39:
|
||||
description = (
|
||||
"Never make a digital copy of your shares and never upload them online."
|
||||
)
|
||||
else:
|
||||
description = (
|
||||
"Never make a digital copy of your seed and never upload it online."
|
||||
)
|
||||
await confirm_action(
|
||||
ctx,
|
||||
"backup_warning",
|
||||
"Caution",
|
||||
description=description,
|
||||
description="Never make a digital copy and never upload it online.",
|
||||
verb="I understand",
|
||||
verb_cancel=None,
|
||||
br_code=ButtonRequestType.ResetDevice,
|
||||
|
@ -155,7 +155,6 @@ def process_face(
|
||||
)
|
||||
nonprintable += " };\n"
|
||||
|
||||
|
||||
yMin = bearingY - rows
|
||||
yMax = yMin + rows
|
||||
font_ymin = min(font_ymin, yMin)
|
||||
@ -178,7 +177,10 @@ def process_face(
|
||||
f.write("#endif\n")
|
||||
|
||||
f.write("#define Font_%s_%s_%d_HEIGHT %d\n" % (name, style, size, size))
|
||||
f.write("#define Font_%s_%s_%d_MAX_HEIGHT %d\n" % (name, style, size, font_ymax - font_ymin))
|
||||
f.write(
|
||||
"#define Font_%s_%s_%d_MAX_HEIGHT %d\n"
|
||||
% (name, style, size, font_ymax - font_ymin)
|
||||
)
|
||||
f.write("#define Font_%s_%s_%d_BASELINE %d\n" % (name, style, size, -font_ymin))
|
||||
f.write(
|
||||
"extern const uint8_t* const Font_%s_%s_%d[%d + 1 - %d];\n"
|
||||
|
Loading…
Reference in New Issue
Block a user