1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-04 11:51:50 +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 super::layout::{LayoutFit, TextLayout, TextStyle};
use heapless::String;
/// Used as an upper bound of number of different styles we may render on single /// Used as an upper bound of number of different styles we may render on single
/// page. /// page.
@ -656,7 +655,7 @@ where
color: Color, color: Color,
target: &mut impl Renderer<'s>, 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()) shape::Text::new(base_point, numeral.as_str())
.with_font(Font::NORMAL) .with_font(Font::NORMAL)
.with_fg(color) .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; pub mod animation;
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
pub mod backlight; pub mod backlight;
@ -14,7 +11,6 @@ pub mod flow;
pub mod geometry; pub mod geometry;
pub mod lerp; pub mod lerp;
pub mod shape; pub mod shape;
#[macro_use]
pub mod util; pub mod util;
pub mod layout; pub mod layout;

View File

@ -4,6 +4,7 @@
use crate::strutil::TString; use crate::strutil::TString;
use crate::ui::util::include_res;
const ICON_APPLE: &[u8] = include_res!("model_mercury/res/fido/icon_apple.toif"); 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::strutil::TString;
use crate::ui::util::include_res;
<% <%
icons: list[tuple[str, str]] = [] icons: list[tuple[str, str]] = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@
use crate::strutil::TString; use crate::strutil::TString;
use crate::ui::util::include_res;
const ICON_APPLE: &[u8] = include_res!("model_tt/res/fido/icon_apple.toif"); 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::strutil::TString;
use crate::ui::util::include_res;
<% <%
icons: list[tuple[str, str]] = [] icons: list[tuple[str, str]] = []

View File

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

View File

@ -8,8 +8,9 @@ use crate::{
text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle}, text::{layout::Chunks, LineBreaking, PageBreaking, TextStyle},
FixedHeightBar, FixedHeightBar,
}, },
display::{Color, Font, Icon}, display::{Color, Font},
geometry::{Insets, Offset}, 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 remaining_available_width = available_width - ellipsis_width;
let chars_from_right = text_font.longest_suffix(remaining_available_width, text); 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. /// Create the `Icon` constant with given name and path.
/// Possibly users can supply `true` as a third argument and this /// Possibly users can supply `true` as a third argument and this
/// will signify that the icon has empty right column. /// will signify that the icon has empty right column.
macro_rules! include_icon { macro_rules! include_icon {
($name:ident, $path:expr, empty_right_col = $empty:expr) => { ($name:ident, $path:expr, empty_right_col = $empty:expr) => {
pub const $name: Icon = if $empty { pub const $name: $crate::ui::display::toif::Icon = if $empty {
Icon::debug_named(include_res!($path), stringify!($name)).with_empty_right_column() $crate::ui::display::toif::Icon::debug_named(
$crate::ui::util::include_res!($path),
stringify!($name),
)
.with_empty_right_column()
} else { } 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. // No empty right column by default.
@ -176,6 +185,14 @@ macro_rules! include_icon {
include_icon!($name, $path, empty_right_col = false); 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); pub const SLIDE_DURATION_MS: Duration = Duration::from_millis(333);