1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-20 06:28:09 +00:00

feat(core/rust): improve and use coinjoin loading screens

[no changelog]
This commit is contained in:
grdddj 2023-06-15 15:40:07 +02:00 committed by Jiří Musil
parent 6a86527882
commit b96b9d43bb
8 changed files with 97 additions and 45 deletions

View File

@ -15,7 +15,7 @@ use super::{
/// ///
/// If it fits, returns the rest of the area. /// If it fits, returns the rest of the area.
/// If it does not fit, returns `None`. /// If it does not fit, returns `None`.
pub fn text_multiline_split_words( pub fn text_multiline(
area: Rect, area: Rect,
text: &str, text: &str,
font: Font, font: Font,
@ -33,3 +33,33 @@ pub fn text_multiline_split_words(
LayoutFit::OutOfBounds { .. } => None, 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<Rect> {
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
}
}
}

View File

@ -2,31 +2,36 @@ use crate::{
strutil::StringType, strutil::StringType,
ui::{ ui::{
component::{ 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, display::Font,
geometry::{Alignment, Rect}, geometry::{Alignment, Insets, Rect},
}, },
}; };
use super::theme; use super::theme;
const HEADER: &str = "COINJOIN IN PROGRESS"; 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<T> { pub struct CoinJoinProgress<T> {
text: T, text: T,
area: Rect, area: Rect,
indeterminate: bool,
} }
impl<T> CoinJoinProgress<T> impl<T> CoinJoinProgress<T>
where where
T: StringType, T: StringType,
{ {
pub fn new(text: T, _indeterminate: bool) -> Self { pub fn new(text: T, indeterminate: bool) -> Self {
Self { Self {
text, text,
area: Rect::zero(), area: Rect::zero(),
indeterminate,
} }
} }
} }
@ -47,33 +52,34 @@ where
} }
fn paint(&mut self) { fn paint(&mut self) {
// Trying to paint all three parts into the area, stopping if any of them // TOP
// doesn't fit. if self.indeterminate {
let mut possible_rest = text_multiline_split_words( text_multiline(
self.area,
HEADER,
Font::BOLD,
theme::FG,
theme::BG,
Alignment::Center,
);
}
// CENTER
// BOTTOM
let top_rest = text_multiline_bottom(
self.area, self.area,
HEADER, FOOTER,
Font::BOLD, Font::BOLD,
theme::FG, theme::FG,
theme::BG, theme::BG,
Alignment::Center, Alignment::Center,
); );
if let Some(rest) = possible_rest { if let Some(rest) = top_rest {
possible_rest = text_multiline_split_words( text_multiline_bottom(
rest, rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)),
self.text.as_ref(), self.text.as_ref(),
Font::MONO, Font::NORMAL,
theme::FG,
theme::BG,
Alignment::Center,
);
} else {
return;
}
if let Some(rest) = possible_rest {
text_multiline_split_words(
rest,
FOOTER,
Font::BOLD,
theme::FG, theme::FG,
theme::BG, theme::BG,
Alignment::Center, Alignment::Center,

View File

@ -3,7 +3,7 @@ use crate::{
trezorhal::usb::usb_configured, trezorhal::usb::usb_configured,
ui::{ ui::{
component::{Child, Component, Event, EventCtx, Label}, component::{Child, Component, Event, EventCtx, Label},
display::{rect_fill, toif::Toif, Font}, display::{rect_fill, toif::Toif, Font, Icon},
event::USBEvent, event::USBEvent,
geometry::{Alignment2D, Insets, Offset, Point, Rect}, geometry::{Alignment2D, Insets, Offset, Point, Rect},
layout::util::get_user_custom_image, 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 LOCK_ICON_TOP_MARGIN: i16 = 12;
const NOTIFICATION_HEIGHT: i16 = 12; const NOTIFICATION_HEIGHT: i16 = 12;
const LABEL_OUTSET: i16 = 3; const LABEL_OUTSET: i16 = 3;
const NOTIFICATION_FONT: Font = Font::NORMAL;
const NOTIFICATION_ICON: Icon = theme::ICON_WARNING;
pub struct Homescreen<T> pub struct Homescreen<T>
where where
@ -66,16 +68,32 @@ where
} }
fn paint_notification(&self) { 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() { if !usb_configured() {
self.fill_notification_background(); self.fill_notification_background();
// TODO: fill warning icons here as well? // 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 { } else if let Some((notification, _level)) = &self.notification {
self.fill_notification_background(); self.fill_notification_background();
// TODO: what if the notification text is so long it collides with icons? display_center(baseline, &notification.as_ref(), NOTIFICATION_FONT);
self.paint_warning_icons_in_top_corners(); // Painting warning icons in top corners when the text is short enough not to
display_center(baseline, &notification.as_ref(), Font::MONO); // 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,
);
}
} }
} }

View File

@ -2,8 +2,7 @@ use crate::{
strutil::StringType, strutil::StringType,
ui::{ ui::{
component::{ component::{
text::util::text_multiline_split_words, Child, Component, Event, EventCtx, Never, text::util::text_multiline, Child, Component, Event, EventCtx, Never, Paginate,
Paginate,
}, },
display::Font, display::Font,
geometry::{Alignment, Offset, Rect}, geometry::{Alignment, Offset, Rect},
@ -118,7 +117,7 @@ where
/// Shows text in the main screen area. /// Shows text in the main screen area.
fn render_text_on_screen(&self, text: &str, font: Font) { fn render_text_on_screen(&self, text: &str, font: Font) {
text_multiline_split_words( text_multiline(
self.area.split_top(INFO_TOP_OFFSET).1, self.area.split_top(INFO_TOP_OFFSET).1,
text, text,
font, font,

View File

@ -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_rounds: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?;
let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.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([ 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_MONO, max_rounds),
Paragraph::new(&theme::TEXT_BOLD, "Max mining fee".into()).no_break(), Paragraph::new(&theme::TEXT_BOLD, "Max mining fee".into())
Paragraph::new(&theme::TEXT_MONO, max_feerate), .with_bottom_padding(2)
.no_break(),
Paragraph::new(&theme::TEXT_MONO, max_feerate).with_bottom_padding(2),
]); ]);
content_in_button_page( content_in_button_page(

View File

@ -272,7 +272,7 @@ async def handle_UnlockPath(msg: UnlockPath) -> protobuf.MessageType:
"confirm_coinjoin_access", "confirm_coinjoin_access",
title="Coinjoin", title="Coinjoin",
description="Access your coinjoin account?", description="Access your coinjoin account?",
verb="ALLOW", verb="ACCESS",
) )
wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx) wire_types = (MessageType.GetAddress, MessageType.GetPublicKey, MessageType.SignTx)

View File

@ -24,7 +24,6 @@ async def homescreen() -> None:
notification = None notification = None
notification_is_error = False notification_is_error = False
if is_set_any_session(MessageType.AuthorizeCoinJoin): if is_set_any_session(MessageType.AuthorizeCoinJoin):
# TODO: is too long for TR
notification = "COINJOIN AUTHORIZED" notification = "COINJOIN AUTHORIZED"
elif storage.device.is_initialized() and storage.device.no_backup(): elif storage.device.is_initialized() and storage.device.no_backup():
notification = "SEEDLESS" notification = "SEEDLESS"
@ -37,7 +36,6 @@ async def homescreen() -> None:
elif storage.device.is_initialized() and not config.has_pin(): elif storage.device.is_initialized() and not config.has_pin():
notification = "PIN NOT SET" notification = "PIN NOT SET"
elif storage.device.get_experimental_features(): elif storage.device.get_experimental_features():
# TODO: is too long for TR
notification = "EXPERIMENTAL MODE" notification = "EXPERIMENTAL MODE"
await Homescreen( await Homescreen(

View File

@ -49,11 +49,9 @@ def bitcoin_progress(description: str) -> ProgressLayout:
def coinjoin_progress(message: str) -> ProgressLayout: def coinjoin_progress(message: str) -> ProgressLayout:
# TODO: create show_progress_coinjoin for TR return RustProgress(
return progress("Coinjoin", message) 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: def pin_progress(message: str, description: str) -> ProgressLayout: