diff --git a/core/embed/rust/src/ui/component/text/util.rs b/core/embed/rust/src/ui/component/text/util.rs index d875d5ea4a..83fda4126e 100644 --- a/core/embed/rust/src/ui/component/text/util.rs +++ b/core/embed/rust/src/ui/component/text/util.rs @@ -15,7 +15,7 @@ use super::{ /// /// If it fits, returns the rest of the area. /// If it does not fit, returns `None`. -pub fn text_multiline_split_words( +pub fn text_multiline( area: Rect, text: &str, font: Font, @@ -33,3 +33,33 @@ pub fn text_multiline_split_words( LayoutFit::OutOfBounds { .. } => None, } } + +/// Same as `text_multiline` above, but aligns the text to the bottom of the +/// area. +pub fn text_multiline_bottom( + area: Rect, + text: &str, + font: Font, + fg_color: Color, + bg_color: Color, + alignment: Alignment, +) -> Option { + let text_style = TextStyle::new(font, fg_color, bg_color, fg_color, fg_color); + let mut text_layout = TextLayout::new(text_style) + .with_bounds(area) + .with_align(alignment); + // When text fits the area, displaying it in the bottom part. + // When not, render it "normally". + match text_layout.fit_text(text) { + LayoutFit::Fitting { height, .. } => { + let (top, bottom) = area.split_bottom(height); + text_layout = text_layout.with_bounds(bottom); + text_layout.render_text(text); + Some(top) + } + LayoutFit::OutOfBounds { .. } => { + text_layout.render_text(text); + None + } + } +} diff --git a/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs b/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs index 2151844ef1..905c5bf87d 100644 --- a/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs +++ b/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs @@ -2,31 +2,36 @@ use crate::{ strutil::StringType, ui::{ component::{ - base::Never, text::util::text_multiline_split_words, Component, Event, EventCtx, + base::Never, + text::util::{text_multiline, text_multiline_bottom}, + Component, Event, EventCtx, }, display::Font, - geometry::{Alignment, Rect}, + geometry::{Alignment, Insets, Rect}, }, }; use super::theme; const HEADER: &str = "COINJOIN IN PROGRESS"; -const FOOTER: &str = "Don't disconnect your Trezor"; +const FOOTER: &str = "Do not disconnect your Trezor!"; +const FOOTER_TEXT_MARGIN: i16 = 8; pub struct CoinJoinProgress { text: T, area: Rect, + indeterminate: bool, } impl CoinJoinProgress where T: StringType, { - pub fn new(text: T, _indeterminate: bool) -> Self { + pub fn new(text: T, indeterminate: bool) -> Self { Self { text, area: Rect::zero(), + indeterminate, } } } @@ -47,33 +52,34 @@ where } fn paint(&mut self) { - // Trying to paint all three parts into the area, stopping if any of them - // doesn't fit. - let mut possible_rest = text_multiline_split_words( + // TOP + if self.indeterminate { + text_multiline( + self.area, + HEADER, + Font::BOLD, + theme::FG, + theme::BG, + Alignment::Center, + ); + } + + // CENTER + + // BOTTOM + let top_rest = text_multiline_bottom( self.area, - HEADER, + FOOTER, Font::BOLD, theme::FG, theme::BG, Alignment::Center, ); - if let Some(rest) = possible_rest { - possible_rest = text_multiline_split_words( - rest, + if let Some(rest) = top_rest { + text_multiline_bottom( + rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)), self.text.as_ref(), - Font::MONO, - theme::FG, - theme::BG, - Alignment::Center, - ); - } else { - return; - } - if let Some(rest) = possible_rest { - text_multiline_split_words( - rest, - FOOTER, - Font::BOLD, + Font::NORMAL, theme::FG, theme::BG, Alignment::Center, diff --git a/core/embed/rust/src/ui/model_tr/component/homescreen.rs b/core/embed/rust/src/ui/model_tr/component/homescreen.rs index 8e1818b740..f941d4ffae 100644 --- a/core/embed/rust/src/ui/model_tr/component/homescreen.rs +++ b/core/embed/rust/src/ui/model_tr/component/homescreen.rs @@ -3,7 +3,7 @@ use crate::{ trezorhal::usb::usb_configured, ui::{ component::{Child, Component, Event, EventCtx, Label}, - display::{rect_fill, toif::Toif, Font}, + display::{rect_fill, toif::Toif, Font, Icon}, event::USBEvent, geometry::{Alignment2D, Insets, Offset, Point, Rect}, layout::util::get_user_custom_image, @@ -25,6 +25,8 @@ const LOGO_ICON_TOP_MARGIN: i16 = 12; const LOCK_ICON_TOP_MARGIN: i16 = 12; const NOTIFICATION_HEIGHT: i16 = 12; const LABEL_OUTSET: i16 = 3; +const NOTIFICATION_FONT: Font = Font::NORMAL; +const NOTIFICATION_ICON: Icon = theme::ICON_WARNING; pub struct Homescreen where @@ -66,16 +68,32 @@ where } fn paint_notification(&self) { - let baseline = TOP_CENTER + Offset::y(Font::MONO.line_height()); + let baseline = TOP_CENTER + Offset::y(NOTIFICATION_FONT.line_height()); if !usb_configured() { self.fill_notification_background(); // TODO: fill warning icons here as well? - display_center(baseline, &"NO USB CONNECTION", Font::MONO); + display_center(baseline, &"NO USB CONNECTION", NOTIFICATION_FONT); } else if let Some((notification, _level)) = &self.notification { self.fill_notification_background(); - // TODO: what if the notification text is so long it collides with icons? - self.paint_warning_icons_in_top_corners(); - display_center(baseline, ¬ification.as_ref(), Font::MONO); + display_center(baseline, ¬ification.as_ref(), NOTIFICATION_FONT); + // Painting warning icons in top corners when the text is short enough not to + // collide with them + let icon_width = NOTIFICATION_ICON.toif.width(); + let text_width = NOTIFICATION_FONT.text_width(notification.as_ref()); + if AREA.width() >= text_width + (icon_width + 1) * 2 { + NOTIFICATION_ICON.draw( + AREA.top_left(), + Alignment2D::TOP_LEFT, + theme::FG, + theme::BG, + ); + NOTIFICATION_ICON.draw( + AREA.top_right(), + Alignment2D::TOP_RIGHT, + theme::FG, + theme::BG, + ); + } } } diff --git a/core/embed/rust/src/ui/model_tr/component/share_words.rs b/core/embed/rust/src/ui/model_tr/component/share_words.rs index f67eb4ef4a..5abb962dc4 100644 --- a/core/embed/rust/src/ui/model_tr/component/share_words.rs +++ b/core/embed/rust/src/ui/model_tr/component/share_words.rs @@ -2,8 +2,7 @@ use crate::{ strutil::StringType, ui::{ component::{ - text::util::text_multiline_split_words, Child, Component, Event, EventCtx, Never, - Paginate, + text::util::text_multiline, Child, Component, Event, EventCtx, Never, Paginate, }, display::Font, geometry::{Alignment, Offset, Rect}, @@ -118,7 +117,7 @@ where /// Shows text in the main screen area. fn render_text_on_screen(&self, text: &str, font: Font) { - text_multiline_split_words( + text_multiline( self.area.split_top(INFO_TOP_OFFSET).1, text, font, diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index eb0c05f776..3006ade01a 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -958,11 +958,14 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut let max_rounds: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?; let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; + // Decreasing bottom padding between paragraphs to fit one screen let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_BOLD, "Max rounds".into()), + Paragraph::new(&theme::TEXT_BOLD, "Max rounds".into()).with_bottom_padding(2), Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new(&theme::TEXT_BOLD, "Max mining fee".into()).no_break(), - Paragraph::new(&theme::TEXT_MONO, max_feerate), + Paragraph::new(&theme::TEXT_BOLD, "Max mining fee".into()) + .with_bottom_padding(2) + .no_break(), + Paragraph::new(&theme::TEXT_MONO, max_feerate).with_bottom_padding(2), ]); content_in_button_page( diff --git a/core/src/apps/base.py b/core/src/apps/base.py index fa4c6341e1..bf83fe01aa 100644 --- a/core/src/apps/base.py +++ b/core/src/apps/base.py @@ -272,7 +272,7 @@ async def handle_UnlockPath(msg: UnlockPath) -> protobuf.MessageType: "confirm_coinjoin_access", title="Coinjoin", description="Access your coinjoin account?", - verb="ALLOW", + verb="ACCESS", ) wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx) diff --git a/core/src/apps/homescreen/__init__.py b/core/src/apps/homescreen/__init__.py index 78b0c8066a..3f138e8db7 100644 --- a/core/src/apps/homescreen/__init__.py +++ b/core/src/apps/homescreen/__init__.py @@ -24,7 +24,6 @@ async def homescreen() -> None: notification = None notification_is_error = False if is_set_any_session(MessageType.AuthorizeCoinJoin): - # TODO: is too long for TR notification = "COINJOIN AUTHORIZED" elif storage.device.is_initialized() and storage.device.no_backup(): notification = "SEEDLESS" @@ -37,7 +36,6 @@ async def homescreen() -> None: elif storage.device.is_initialized() and not config.has_pin(): notification = "PIN NOT SET" elif storage.device.get_experimental_features(): - # TODO: is too long for TR notification = "EXPERIMENTAL MODE" await Homescreen( diff --git a/core/src/trezor/ui/layouts/tr/progress.py b/core/src/trezor/ui/layouts/tr/progress.py index 1c02824126..e92801b1bf 100644 --- a/core/src/trezor/ui/layouts/tr/progress.py +++ b/core/src/trezor/ui/layouts/tr/progress.py @@ -49,11 +49,9 @@ def bitcoin_progress(description: str) -> ProgressLayout: def coinjoin_progress(message: str) -> ProgressLayout: - # TODO: create show_progress_coinjoin for TR - return progress("Coinjoin", message) - # return RustProgress( - # layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False) - # ) + return RustProgress( + layout=trezorui2.show_progress_coinjoin(title=message, indeterminate=False) + ) def pin_progress(message: str, description: str) -> ProgressLayout: