1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-14 03:30:02 +00:00

refactor(core/rust): drop ui::macros

removing inttostr!, build_string!, and relocating include_res!
This commit is contained in:
matejcik 2024-06-13 15:09:43 +02:00 committed by matejcik
parent 8134490e2e
commit da37bce59d
20 changed files with 101 additions and 130 deletions

View File

@ -14,7 +14,6 @@ use crate::{
};
use super::layout::{LayoutFit, TextLayout, TextStyle};
use heapless::String;
/// Used as an upper bound of number of different styles we may render on single
/// page.
@ -656,7 +655,7 @@ where
color: Color,
target: &mut impl Renderer<'s>,
) {
let numeral = build_string!(10, inttostr!(n as u8 + 1), ".");
let numeral = uformat!("{}.", n + 1);
shape::Text::new(base_point, numeral.as_str())
.with_font(Font::NORMAL)
.with_fg(color)

View File

@ -1,25 +0,0 @@
#[allow(unused_macros)] // T1 doesn't use icons (yet)
macro_rules! include_res {
($filename:expr) => {
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/ui/", $filename))
};
}
/// Concatenates arbitrary amount of slices into a String.
macro_rules! build_string {
($max:expr, $($string:expr),+) => {
{
let mut new_string = String::<$max>::new();
$(unwrap!(new_string.push_str($string));)+
new_string
}
}
}
/// Transforms integer into string slice. For example for printing.
#[allow(unused_macros)] // not used in TT UI
macro_rules! inttostr {
($int:expr) => {{
unwrap!(heapless::String::<10>::try_from($int)).as_str()
}};
}

View File

@ -1,6 +1,3 @@
#[macro_use]
pub mod macros;
pub mod animation;
#[cfg(feature = "micropython")]
pub mod backlight;
@ -14,7 +11,6 @@ pub mod flow;
pub mod geometry;
pub mod lerp;
pub mod shape;
#[macro_use]
pub mod util;
pub mod layout;

View File

@ -4,6 +4,7 @@
use crate::strutil::TString;
use crate::ui::util::include_res;
const ICON_APPLE: &[u8] = include_res!("model_mercury/res/fido/icon_apple.toif");

View File

@ -4,6 +4,7 @@
use crate::strutil::TString;
use crate::ui::util::include_res;
<%
icons: list[tuple[str, str]] = []

View File

@ -9,12 +9,11 @@ use crate::{
event::SwipeEvent,
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
model_mercury::component::Footer,
shape,
shape::Renderer,
shape::{self, Renderer},
util,
},
};
use heapless::{String, Vec};
use heapless::Vec;
const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less
const ANIMATION_DURATION_MS: Duration = Duration::from_millis(166);
@ -158,7 +157,7 @@ impl<'a> Component for ShareWords<'a> {
.text_font
.visible_text_height("1"),
);
let ordinal = build_string!(3, inttostr!(ordinal_val), ".");
let ordinal = uformat!("{}.", ordinal_val);
shape::Text::new(ordinal_pos, &ordinal)
.with_font(theme::TEXT_SUB_GREY_LIGHT.text_font)
.with_fg(theme::GREY)
@ -204,8 +203,7 @@ impl<'a> crate::trace::Trace for ShareWords<'a> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ShareWords");
let word = &self.share_words[self.page_index as usize];
let content =
word.map(|w| build_string!(50, inttostr!(self.page_index as u8 + 1), ". ", w, "\n"));
let content = word.map(|w| uformat!("{}. {}\n", self.page_index + 1, w));
t.string("screen_content", content.as_str().into());
t.int("page_count", self.share_words.len() as i64)
}

View File

@ -2,7 +2,7 @@ use crate::ui::{
component::{text::TextStyle, LineBreaking::BreakWordsNoHyphen},
constant::{HEIGHT, WIDTH},
display::{Color, Font},
geometry::{Offset, Point, Rect},
geometry::{Offset, Point, Rect}, util::include_res,
};
use super::super::{

View File

@ -9,8 +9,8 @@ use crate::{
text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle},
FixedHeightBar,
},
display::{Color, Font, Icon},
geometry::Insets,
display::{Color, Font},
geometry::Insets, util::include_icon,
},
};

View File

@ -1,10 +1,12 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::Font,
geometry::{Alignment, Point, Rect},
shape,
shape::Renderer,
util::long_line_content_with_ellipsis,
use crate::{
strutil::ShortString,
ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::Font,
geometry::{Alignment, Point, Rect},
shape::{self, Renderer},
util::long_line_content_with_ellipsis,
},
};
use super::{common, theme};
@ -12,9 +14,9 @@ use super::{common, theme};
/// Component that allows for "allocating" a standalone line of text anywhere
/// on the screen and updating it arbitrarily - without affecting the rest
/// and without being affected by other components.
pub struct ChangingTextLine<T> {
pub struct ChangingTextLine {
pad: Pad,
text: T,
text: ShortString,
font: Font,
/// Whether to show the text. Can be disabled.
show_content: bool,
@ -25,11 +27,10 @@ pub struct ChangingTextLine<T> {
text_at_the_top: bool,
}
impl<T> ChangingTextLine<T>
where
T: AsRef<str>,
{
pub fn new(text: T, font: Font, alignment: Alignment) -> Self {
impl ChangingTextLine {
pub fn new(text: &str, font: Font, alignment: Alignment, max_len: usize) -> Self {
let text = unwrap!(ShortString::try_from(text));
debug_assert!(text.capacity() >= max_len);
Self {
pad: Pad::with_background(theme::BG),
text,
@ -41,12 +42,12 @@ where
}
}
pub fn center_mono(text: T) -> Self {
Self::new(text, Font::MONO, Alignment::Center)
pub fn center_mono(text: &str, max_len: usize) -> Self {
Self::new(text, Font::MONO, Alignment::Center, max_len)
}
pub fn center_bold(text: T) -> Self {
Self::new(text, Font::BOLD_UPPER, Alignment::Center)
pub fn center_bold(text: &str, max_len: usize) -> Self {
Self::new(text, Font::BOLD_UPPER, Alignment::Center, max_len)
}
/// Not showing ellipsis at the beginning of longer texts.
@ -62,13 +63,14 @@ where
}
/// Update the text to be displayed in the line.
pub fn update_text(&mut self, text: T) {
self.text = text;
pub fn update_text(&mut self, text: &str) {
self.text.clear();
unwrap!(self.text.push_str(text));
}
/// Get current text.
pub fn get_text(&self) -> &T {
&self.text
pub fn get_text(&self) -> &str {
self.text.as_str()
}
/// Changing the current font
@ -155,21 +157,14 @@ where
// Creating the notion of motion by shifting the text left and right with
// each new text character.
// (So that it is apparent for the user that the text is changing.)
let x_offset = if self.text.as_ref().len() % 2 == 0 {
0
} else {
2
};
let x_offset = if self.text.len() % 2 == 0 { 0 } else { 2 };
let baseline = Point::new(self.pad.area.x0 + x_offset, self.y_baseline());
common::display_left(baseline, &text_to_display, self.font);
}
}
impl<T> Component for ChangingTextLine<T>
where
T: AsRef<str>,
{
impl Component for ChangingTextLine {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {

View File

@ -1,5 +1,5 @@
use crate::{
strutil::TString,
strutil::{ShortString, TString},
translations::TR,
trezorhal::random,
ui::{
@ -270,7 +270,7 @@ impl ChoiceFactory for ChoiceFactoryPassphrase {
/// Component for entering a passphrase.
pub struct PassphraseEntry {
choice_page: ChoicePage<ChoiceFactoryPassphrase, PassphraseAction>,
passphrase_dots: Child<ChangingTextLine<String<MAX_PASSPHRASE_LENGTH>>>,
passphrase_dots: Child<ChangingTextLine>,
show_plain_passphrase: bool,
show_last_digit: bool,
textbox: TextBox<MAX_PASSPHRASE_LENGTH>,
@ -283,7 +283,7 @@ impl PassphraseEntry {
choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true))
.with_carousel(true)
.with_initial_page_counter(random_menu_position()),
passphrase_dots: Child::new(ChangingTextLine::center_mono(String::new())),
passphrase_dots: Child::new(ChangingTextLine::center_mono("", MAX_PASSPHRASE_LENGTH)),
show_plain_passphrase: false,
show_last_digit: false,
textbox: TextBox::empty(),
@ -298,7 +298,7 @@ impl PassphraseEntry {
unwrap!(String::try_from(""))
} else {
// Showing asterisks and possibly the last digit.
let mut dots: String<MAX_PASSPHRASE_LENGTH> = String::new();
let mut dots: ShortString = String::new();
for _ in 0..self.textbox.len() - 1 {
unwrap!(dots.push('*'));
}
@ -311,7 +311,7 @@ impl PassphraseEntry {
dots
};
self.passphrase_dots.mutate(ctx, |ctx, passphrase_dots| {
passphrase_dots.update_text(text_to_show);
passphrase_dots.update_text(&text_to_show);
passphrase_dots.request_complete_repaint(ctx);
});
}

View File

@ -1,5 +1,5 @@
use crate::{
strutil::TString,
strutil::{ShortString, TString},
translations::TR,
trezorhal::random,
ui::{
@ -132,8 +132,8 @@ impl ChoiceFactory for ChoiceFactoryPIN {
/// Component for entering a PIN.
pub struct PinEntry<'a> {
choice_page: ChoicePage<ChoiceFactoryPIN, PinAction>,
header_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
pin_line: Child<ChangingTextLine<String<MAX_PIN_LENGTH>>>,
header_line: Child<ChangingTextLine>,
pin_line: Child<ChangingTextLine>,
prompt: TString<'a>,
subprompt: TString<'a>,
/// Whether we already show the "real" prompt (not the warning).
@ -151,20 +151,13 @@ impl<'a> PinEntry<'a> {
// any button click.)
let show_subprompt = !subprompt.is_empty();
let (showing_real_prompt, header_line_content, pin_line_content) = if show_subprompt {
(
false,
TR::pin__title_wrong_pin.map_translated(|t| unwrap!(String::try_from(t))),
subprompt.map(|s| unwrap!(String::try_from(s))),
)
(false, TR::pin__title_wrong_pin.into(), subprompt)
} else {
(
true,
prompt.map(|s| unwrap!(String::try_from(s))),
unwrap!(String::try_from(EMPTY_PIN_STR)),
)
(true, prompt, EMPTY_PIN_STR.into())
};
let mut pin_line = ChangingTextLine::center_bold(pin_line_content).without_ellipsis();
let mut pin_line = pin_line_content
.map(|s| ChangingTextLine::center_bold(s, MAX_PIN_LENGTH).without_ellipsis());
if show_subprompt {
pin_line.update_font(Font::NORMAL);
}
@ -175,7 +168,8 @@ impl<'a> PinEntry<'a> {
.with_initial_page_counter(get_random_digit_position())
.with_carousel(true),
header_line: Child::new(
ChangingTextLine::center_bold(header_line_content)
header_line_content
.map(|s| ChangingTextLine::center_bold(s, MAX_PIN_LENGTH))
.without_ellipsis()
.with_text_at_the_top(),
),
@ -209,7 +203,7 @@ impl<'a> PinEntry<'a> {
unwrap!(String::try_from(self.pin()))
} else {
// Showing asterisks and possibly the last digit.
let mut dots: String<MAX_PIN_LENGTH> = String::new();
let mut dots = ShortString::new();
for _ in 0..self.textbox.len() - 1 {
unwrap!(dots.push('*'));
}
@ -224,7 +218,7 @@ impl<'a> PinEntry<'a> {
self.pin_line.mutate(ctx, |ctx, pin_line| {
pin_line.update_font(used_font);
pin_line.update_text(pin_line_text);
pin_line.update_text(&pin_line_text);
pin_line.request_complete_repaint(ctx);
});
}
@ -232,7 +226,7 @@ impl<'a> PinEntry<'a> {
/// Showing the real prompt instead of WRONG PIN
fn show_prompt(&mut self, ctx: &mut EventCtx) {
self.header_line.mutate(ctx, |ctx, header_line| {
header_line.update_text(self.prompt.map(|s| unwrap!(String::try_from(s))));
self.prompt.map(|s| header_line.update_text(s));
header_line.request_complete_repaint(ctx);
});
}

View File

@ -20,6 +20,7 @@ enum WordlistAction {
}
const MAX_WORD_LENGTH: usize = 10;
const LINE_CAPACITY: usize = MAX_WORD_LENGTH + 1;
/// Offer words when there will be fewer of them than this
const OFFER_WORDS_THRESHOLD: usize = 10;
@ -156,7 +157,7 @@ impl ChoiceFactory for ChoiceFactoryWordlist {
/// Component for entering a mnemonic from a wordlist - BIP39 or SLIP39.
pub struct WordlistEntry {
choice_page: ChoicePage<ChoiceFactoryWordlist, WordlistAction>,
chosen_letters: Child<ChangingTextLine<String<{ MAX_WORD_LENGTH + 1 }>>>,
chosen_letters: Child<ChangingTextLine>,
textbox: TextBox<MAX_WORD_LENGTH>,
offer_words: bool,
wordlist_type: WordlistType,
@ -174,9 +175,7 @@ impl WordlistEntry {
.with_incomplete(true)
.with_carousel(true)
.with_initial_page_counter(get_random_position(choices_count)),
chosen_letters: Child::new(ChangingTextLine::center_mono(unwrap!(String::try_from(
PROMPT
)))),
chosen_letters: Child::new(ChangingTextLine::center_mono(PROMPT, LINE_CAPACITY)),
textbox: TextBox::empty(),
offer_words: false,
wordlist_type,
@ -196,9 +195,7 @@ impl WordlistEntry {
choice_page: ChoicePage::new(choices)
.with_incomplete(true)
.with_initial_page_counter(1),
chosen_letters: Child::new(ChangingTextLine::center_mono(unwrap!(String::try_from(
word
)))),
chosen_letters: Child::new(ChangingTextLine::center_mono(word, LINE_CAPACITY)),
textbox: TextBox::new(unwrap!(String::try_from(word))),
offer_words: false,
wordlist_type,
@ -263,9 +260,9 @@ impl WordlistEntry {
/// Reflects currently chosen letters in the textbox.
fn update_chosen_letters(&mut self, ctx: &mut EventCtx) {
let text = build_string!({ MAX_WORD_LENGTH + 1 }, self.textbox.content(), PROMPT);
let text = uformat!("{}{}", self.textbox.content(), PROMPT);
self.chosen_letters.mutate(ctx, |ctx, chosen_letters| {
chosen_letters.update_text(text);
chosen_letters.update_text(&text);
chosen_letters.request_complete_repaint(ctx);
});
}

View File

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::{ShortString, StringType, TString},
translations::TR,
ui::{
component::{
@ -8,12 +8,12 @@ use crate::{
},
display::Font,
geometry::{Alignment, Offset, Rect},
shape,
shape::Renderer,
shape::{self, Renderer},
},
};
use heapless::{String, Vec};
use heapless::Vec;
use ufmt::uwrite;
use super::{common::display_left, scrollbar::SCROLLBAR_SPACE, theme, ScrollBar};
@ -72,15 +72,10 @@ where
word_screens + 1
}
fn get_final_text(&self) -> String<50> {
fn get_final_text(&self) -> ShortString {
TR::share_words__wrote_down_all.map_translated(|wrote_down_all| {
TR::share_words__words_in_order.map_translated(|in_order| {
build_string!(
50,
wrote_down_all,
inttostr!(self.share_words.len() as u8),
in_order
)
uformat!("{}{}{}", wrote_down_all, self.share_words.len(), in_order)
})
})
}
@ -124,7 +119,7 @@ where
}
let word = &self.share_words[index];
let baseline = self.area.top_left() + Offset::y(y_offset);
let ordinal = build_string!(5, inttostr!(index as u8 + 1), ".");
let ordinal = uformat!("{}.", index + 1);
display_left(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal, NUMBER_FONT);
display_left(baseline + Offset::x(WORD_X_OFFSET), word, WORD_FONT);
}
@ -142,7 +137,7 @@ where
}
let word = &self.share_words[index];
let baseline = self.area.top_left() + Offset::y(y_offset);
let ordinal = build_string!(5, inttostr!(index as u8 + 1), ".");
let ordinal = uformat!("{}.", index + 1);
shape::Text::new(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal)
.with_font(NUMBER_FONT)
@ -230,16 +225,14 @@ where
let content = if self.is_final_page() {
self.get_final_text()
} else {
let mut content = String::<50>::new();
let mut content = ShortString::new();
for i in 0..WORDS_PER_PAGE {
let index = self.word_index() + i;
if index >= self.share_words.len() {
break;
}
let word = &self.share_words[index];
let current_line =
build_string!(50, inttostr!(index as u8 + 1), ". ", word.as_ref(), "\n");
unwrap!(content.push_str(&current_line));
let word: TString = self.share_words[index].clone().into();
unwrap!(uwrite!(content, "{}. {}\n", index + 1, word));
}
content
};

View File

@ -1,6 +1,7 @@
use crate::ui::{
component::text::TextStyle,
display::{toif::Icon, Color, Font},
display::{Color, Font},
util::include_icon,
};
pub use super::super::theme::{BLACK, WHITE};

View File

@ -3,8 +3,9 @@ use crate::ui::{
text::{layout::Chunks, TextStyle},
LineBreaking, PageBreaking,
},
display::{toif::Icon, Color, Font},
display::{Color, Font},
geometry::Offset,
util::include_icon,
};
use num_traits::FromPrimitive;

View File

@ -4,6 +4,7 @@
use crate::strutil::TString;
use crate::ui::util::include_res;
const ICON_APPLE: &[u8] = include_res!("model_tt/res/fido/icon_apple.toif");

View File

@ -4,6 +4,7 @@
use crate::strutil::TString;
use crate::ui::util::include_res;
<%
icons: list[tuple[str, str]] = []

View File

@ -6,7 +6,7 @@ use crate::ui::{
model_tt::{
component::{ButtonStyle, ButtonStyleSheet, ResultStyle},
theme::{BLACK, FG, GREY_DARK, GREY_LIGHT, WHITE},
},
}, util::include_res,
};
pub const BLD_BG: Color = Color::rgb(0x00, 0x1E, 0xAD);

View File

@ -8,8 +8,9 @@ use crate::{
text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle},
FixedHeightBar,
},
display::{Color, Font, Icon},
display::{Color, Font},
geometry::{Insets, Offset},
util::{include_icon, include_res},
},
};

View File

@ -155,20 +155,29 @@ pub fn long_line_content_with_ellipsis(
let remaining_available_width = available_width - ellipsis_width;
let chars_from_right = text_font.longest_suffix(remaining_available_width, text);
build_string!(50, ellipsis, &text[text.len() - chars_from_right..])
let mut s = ShortString::new();
unwrap!(s.push_str(ellipsis));
unwrap!(s.push_str(&text[text.len() - chars_from_right..]));
s
}
}
#[macro_export]
/// Create the `Icon` constant with given name and path.
/// Possibly users can supply `true` as a third argument and this
/// will signify that the icon has empty right column.
macro_rules! include_icon {
($name:ident, $path:expr, empty_right_col = $empty:expr) => {
pub const $name: Icon = if $empty {
Icon::debug_named(include_res!($path), stringify!($name)).with_empty_right_column()
pub const $name: $crate::ui::display::toif::Icon = if $empty {
$crate::ui::display::toif::Icon::debug_named(
$crate::ui::util::include_res!($path),
stringify!($name),
)
.with_empty_right_column()
} else {
Icon::debug_named(include_res!($path), stringify!($name))
$crate::ui::display::toif::Icon::debug_named(
$crate::ui::util::include_res!($path),
stringify!($name),
)
};
};
// No empty right column by default.
@ -176,6 +185,14 @@ macro_rules! include_icon {
include_icon!($name, $path, empty_right_col = false);
};
}
pub(crate) use include_icon;
macro_rules! include_res {
($filename:expr) => {
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/ui/", $filename))
};
}
pub(crate) use include_res;
pub const SLIDE_DURATION_MS: Duration = Duration::from_millis(333);