1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-27 16:48:09 +00:00

refactor(core/rust) use TString in Label and Button

[no changelog]
This commit is contained in:
tychovrahe 2024-03-26 21:51:59 +01:00 committed by Martin Milata
parent ed6aa48726
commit 256adc3567
43 changed files with 869 additions and 1043 deletions

View File

@ -1,22 +1,22 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display::Font,
geometry::{Alignment, Insets, Offset, Point, Rect},
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Never},
display::Font,
geometry::{Alignment, Insets, Offset, Point, Rect},
},
};
use super::{text::TextStyle, TextLayout};
pub struct Label<T> {
text: T,
pub struct Label<'a> {
text: TString<'a>,
layout: TextLayout,
vertical: Alignment,
}
impl<T> Label<T>
where
T: AsRef<str>,
{
pub fn new(text: T, align: Alignment, style: TextStyle) -> Self {
impl<'a> Label<'a> {
pub fn new(text: TString<'a>, align: Alignment, style: TextStyle) -> Self {
Self {
text,
layout: TextLayout::new(style).with_align(align),
@ -24,15 +24,15 @@ where
}
}
pub fn left_aligned(text: T, style: TextStyle) -> Self {
pub fn left_aligned(text: TString<'a>, style: TextStyle) -> Self {
Self::new(text, Alignment::Start, style)
}
pub fn right_aligned(text: T, style: TextStyle) -> Self {
pub fn right_aligned(text: TString<'a>, style: TextStyle) -> Self {
Self::new(text, Alignment::End, style)
}
pub fn centered(text: T, style: TextStyle) -> Self {
pub fn centered(text: TString<'a>, style: TextStyle) -> Self {
Self::new(text, Alignment::Center, style)
}
@ -41,11 +41,11 @@ where
self
}
pub fn text(&self) -> &T {
pub fn text(&self) -> &TString<'a> {
&self.text
}
pub fn set_text(&mut self, text: T) {
pub fn set_text(&mut self, text: TString<'a>) {
self.text = text;
}
@ -63,22 +63,22 @@ where
pub fn max_size(&self) -> Offset {
let font = self.font();
Offset::new(font.text_width(self.text.as_ref()), font.text_max_height())
let width = self.text.map(|c| font.text_width(c));
Offset::new(width, font.text_max_height())
}
pub fn text_height(&self, width: i16) -> i16 {
let bounds = Rect::from_top_left_and_size(Point::zero(), Offset::new(width, i16::MAX));
self.layout
.with_bounds(bounds)
.fit_text(self.text.as_ref())
.height()
self.text
.map(|c| self.layout.with_bounds(bounds).fit_text(c).height())
}
pub fn text_area(&self) -> Rect {
// XXX only works on single-line labels
assert!(self.layout.bounds.height() <= self.font().text_max_height());
let available_width = self.layout.bounds.width();
let width = self.font().text_width(self.text.as_ref());
let width = self.text.map(|c| self.font().text_width(c));
let height = self.font().text_height();
let cursor = self.layout.initial_cursor();
let baseline = match self.alignment() {
@ -90,18 +90,13 @@ where
}
}
impl<T> Component for Label<T>
where
T: AsRef<str>,
{
impl Component for Label<'_> {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
let height = self
.layout
.with_bounds(bounds)
.fit_text(self.text.as_ref())
.height();
.text
.map(|c| self.layout.with_bounds(bounds).fit_text(c).height());
let diff = bounds.height() - height;
let insets = match self.vertical {
Alignment::Start => Insets::bottom(diff),
@ -117,7 +112,7 @@ where
}
fn paint(&mut self) {
self.layout.render_text(self.text.as_ref());
self.text.map(|c| self.layout.render_text(c));
}
#[cfg(feature = "ui_bounds")]
@ -127,12 +122,9 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Label<T>
where
T: AsRef<str>,
{
impl crate::trace::Trace for Label<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Label");
t.string("text", self.text.as_ref().into());
t.string("text", self.text);
}
}

View File

@ -1,4 +1,5 @@
use crate::{
strutil::TString,
time::{Duration, Instant},
ui::{
animation::Animation,
@ -21,13 +22,13 @@ enum State {
PauseRight,
}
pub struct Marquee<T> {
pub struct Marquee {
area: Rect,
pause_token: Option<TimerToken>,
min_offset: i16,
max_offset: i16,
state: State,
text: T,
text: TString<'static>,
font: Font,
fg: Color,
bg: Color,
@ -35,11 +36,8 @@ pub struct Marquee<T> {
pause: Duration,
}
impl<T> Marquee<T>
where
T: AsRef<str>,
{
pub fn new(text: T, font: Font, fg: Color, bg: Color) -> Self {
impl Marquee {
pub fn new(text: TString<'static>, font: Font, fg: Color, bg: Color) -> Self {
Self {
area: Rect::zero(),
pause_token: None,
@ -55,7 +53,7 @@ where
}
}
pub fn set_text(&mut self, text: T) {
pub fn set_text(&mut self, text: TString<'static>) {
self.text = text;
}
@ -66,7 +64,7 @@ where
}
if let State::Initial = self.state {
let text_width = self.font.text_width(self.text.as_ref());
let text_width = self.text.map(|t| self.font.text_width(t));
let max_offset = self.area.width() - text_width;
self.min_offset = 0;
@ -122,21 +120,12 @@ where
}
pub fn paint_anim(&mut self, offset: i16) {
display::marquee(
self.area,
self.text.as_ref(),
offset,
self.font,
self.fg,
self.bg,
);
self.text
.map(|t| display::marquee(self.area, t, offset, self.font, self.fg, self.bg));
}
}
impl<T> Component for Marquee<T>
where
T: AsRef<str>,
{
impl Component for Marquee {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
@ -228,12 +217,9 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Marquee<T>
where
T: AsRef<str>,
{
impl crate::trace::Trace for Marquee {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Marquee");
t.string("text", self.text.as_ref().into());
t.string("text", self.text);
}
}

View File

@ -1,7 +1,10 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
geometry::{Alignment, Alignment2D, Rect},
layout::simplified::ReturnToC,
use crate::{
strutil::TString,
ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
geometry::{Alignment, Alignment2D, Rect},
layout::simplified::ReturnToC,
},
};
use super::super::{
@ -29,14 +32,14 @@ impl ReturnToC for IntroMsg {
pub struct Intro<'a> {
bg: Pad,
title: Child<Label<&'a str>>,
title: Child<Label<'a>>,
buttons: Child<ButtonController>,
text: Child<Label<&'a str>>,
warn: Option<Child<Label<&'a str>>>,
text: Child<Label<'a>>,
warn: Option<Child<Label<'a>>>,
}
impl<'a> Intro<'a> {
pub fn new(title: &'a str, content: &'a str, fw_ok: bool) -> Self {
pub fn new(title: TString<'a>, content: TString<'a>, fw_ok: bool) -> Self {
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
title: Child::new(Label::centered(title, TEXT_NORMAL).vertically_centered()),
@ -46,7 +49,7 @@ impl<'a> Intro<'a> {
))),
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
warn: (!fw_ok).then_some(Child::new(
Label::new("FIRMWARE CORRUPTED", Alignment::Start, TEXT_NORMAL)
Label::new("FIRMWARE CORRUPTED".into(), Alignment::Start, TEXT_NORMAL)
.vertically_centered(),
)),
}

View File

@ -126,9 +126,10 @@ impl UIFeaturesBootloader for ModelTRFeatures {
unwrap!(reboot_msg.push_str("Reconnect the device"));
}
let title = Label::centered("Firmware installed", TEXT_BOLD).vertically_centered();
let title = Label::centered("Firmware installed".into(), TEXT_BOLD).vertically_centered();
let content = Label::centered(reboot_msg.as_str(), TEXT_NORMAL).vertically_centered();
let content =
Label::centered(reboot_msg.as_str().into(), TEXT_NORMAL).vertically_centered();
let mut frame =
ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, complete_draw);
@ -136,10 +137,10 @@ impl UIFeaturesBootloader for ModelTRFeatures {
}
fn screen_install_fail() {
let title = Label::centered("Install failed", TEXT_BOLD).vertically_centered();
let title = Label::centered("Install failed".into(), TEXT_BOLD).vertically_centered();
let content =
Label::centered("Please reconnect\nthe device", TEXT_NORMAL).vertically_centered();
let content = Label::centered("Please reconnect\nthe device".into(), TEXT_NORMAL)
.vertically_centered();
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true);
show(&mut frame, false);
@ -169,44 +170,68 @@ impl UIFeaturesBootloader for ModelTRFeatures {
"DOWNGRADE FW"
};
let message = Label::left_aligned(version_str.as_str(), TEXT_NORMAL).vertically_centered();
let message =
Label::left_aligned(version_str.as_str().into(), TEXT_NORMAL).vertically_centered();
let fingerprint = Label::left_aligned(
fingerprint,
fingerprint.into(),
TEXT_NORMAL.with_line_breaking(BreakWordsNoHyphen),
)
.vertically_centered();
let alert =
(!should_keep_seed).then_some(Label::left_aligned("Seed will be erased!", TEXT_NORMAL));
let alert = (!should_keep_seed).then_some(Label::left_aligned(
"Seed will be erased!".into(),
TEXT_NORMAL,
));
let mut frame = Confirm::new(BLD_BG, title_str, message, alert, "INSTALL", false)
.with_info_screen("FW FINGERPRINT", fingerprint);
let mut frame = Confirm::new(
BLD_BG,
title_str.into(),
message,
alert,
"INSTALL".into(),
false,
)
.with_info_screen("FW FINGERPRINT".into(), fingerprint);
run(&mut frame)
}
fn screen_wipe_confirm() -> u32 {
let message = Label::left_aligned("Seed and firmware will be erased!", TEXT_NORMAL)
let message = Label::left_aligned("Seed and firmware will be erased!".into(), TEXT_NORMAL)
.vertically_centered();
let mut frame = Confirm::new(BLD_BG, "FACTORY RESET", message, None, "RESET", false);
let mut frame = Confirm::new(
BLD_BG,
"FACTORY RESET".into(),
message,
None,
"RESET".into(),
false,
);
run(&mut frame)
}
fn screen_unlock_bootloader_confirm() -> u32 {
let message =
Label::left_aligned("This action cannot be undone!", TEXT_NORMAL).vertically_centered();
let message = Label::left_aligned("This action cannot be undone!".into(), TEXT_NORMAL)
.vertically_centered();
let mut frame = Confirm::new(BLD_BG, "UNLOCK BOOTLOADER?", message, None, "UNLOCK", true);
let mut frame = Confirm::new(
BLD_BG,
"UNLOCK BOOTLOADER?".into(),
message,
None,
"UNLOCK".into(),
true,
);
run(&mut frame)
}
fn screen_unlock_bootloader_success() {
let title = Label::centered("Bootloader unlocked", TEXT_BOLD).vertically_centered();
let title = Label::centered("Bootloader unlocked".into(), TEXT_BOLD).vertically_centered();
let content =
Label::centered("Please reconnect the\ndevice", TEXT_NORMAL).vertically_centered();
let content = Label::centered("Please reconnect the\ndevice".into(), TEXT_NORMAL)
.vertically_centered();
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, true);
show(&mut frame, false);
@ -227,7 +252,11 @@ impl UIFeaturesBootloader for ModelTRFeatures {
unwrap!(version_str.push_str("\nby "));
unwrap!(version_str.push_str(vendor));
let mut frame = Intro::new(title_str.as_str(), version_str.as_str(), fw_ok);
let mut frame = Intro::new(
title_str.as_str().into(),
version_str.as_str().into(),
fw_ok,
);
run(&mut frame)
}
@ -268,20 +297,20 @@ impl UIFeaturesBootloader for ModelTRFeatures {
}
fn screen_wipe_success() {
let title = Label::centered("Trezor Reset", TEXT_BOLD).vertically_centered();
let title = Label::centered("Trezor Reset".into(), TEXT_BOLD).vertically_centered();
let content =
Label::centered("Please reconnect\nthe device", TEXT_NORMAL).vertically_centered();
let content = Label::centered("Please reconnect\nthe device".into(), TEXT_NORMAL)
.vertically_centered();
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_SPINNER, title, content, true);
show(&mut frame, false);
}
fn screen_wipe_fail() {
let title = Label::centered("Reset failed", TEXT_BOLD).vertically_centered();
let title = Label::centered("Reset failed".into(), TEXT_BOLD).vertically_centered();
let content =
Label::centered("Please reconnect\nthe device", TEXT_NORMAL).vertically_centered();
let content = Label::centered("Please reconnect\nthe device".into(), TEXT_NORMAL)
.vertically_centered();
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true);
show(&mut frame, false);

View File

@ -23,7 +23,7 @@ const QR_BORDER: i16 = 3;
pub struct AddressDetails {
qr_code: Qr,
details_view: Paragraphs<ParagraphVecShort<'static>>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>, StrBuffer>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>,
current_page: usize,
current_subpage: usize,
@ -149,7 +149,7 @@ impl AddressDetails {
fn fill_xpub_page(&mut self, ctx: &mut EventCtx) {
let i = self.current_page - 2;
self.xpub_view.update_title(ctx, self.xpubs[i].0);
self.xpub_view.update_title(ctx, self.xpubs[i].0.into());
self.xpub_view.update_content(ctx, |p| {
p.inner_mut().update(self.xpubs[i].1);
p.change_page(0)

View File

@ -20,14 +20,14 @@ pub enum ConfirmMsg {
Confirm = 2,
}
pub struct Confirm<U> {
pub struct Confirm<'a> {
bg: Pad,
bg_color: Color,
title: TString<'static>,
message: Child<Label<U>>,
alert: Option<Label<U>>,
info_title: Option<TString<'static>>,
info_text: Option<Label<U>>,
title: TString<'a>,
message: Child<Label<'a>>,
alert: Option<Label<'a>>,
info_title: Option<TString<'a>>,
info_text: Option<Label<'a>>,
button_text: TString<'static>,
buttons: ButtonController,
/// Whether we are on the info screen (optional extra screen)
@ -35,25 +35,21 @@ pub struct Confirm<U> {
two_btn_confirm: bool,
}
impl<U> Confirm<U>
where
U: AsRef<str>,
{
pub fn new<T: Into<TString<'static>>>(
impl<'a> Confirm<'a> {
pub fn new(
bg_color: Color,
title: T,
message: Label<U>,
alert: Option<Label<U>>,
button_text: T,
title: TString<'a>,
message: Label<'a>,
alert: Option<Label<'a>>,
button_text: TString<'static>,
two_btn_confirm: bool,
) -> Self {
let button_text = button_text.into();
let btn_layout =
Self::get_button_layout_general(false, button_text, false, two_btn_confirm);
Self {
bg: Pad::with_background(bg_color).with_clear(),
bg_color,
title: title.into(),
title,
message: Child::new(message),
alert,
info_title: None,
@ -66,12 +62,8 @@ where
}
/// Adding optional info screen
pub fn with_info_screen<T: Into<TString<'static>>>(
mut self,
info_title: T,
info_text: Label<U>,
) -> Self {
self.info_title = Some(info_title.into());
pub fn with_info_screen(mut self, info_title: TString<'a>, info_text: Label<'a>) -> Self {
self.info_title = Some(info_title);
self.info_text = Some(info_text);
self.buttons = ButtonController::new(self.get_button_layout());
self
@ -125,10 +117,7 @@ where
}
}
impl<U> Component for Confirm<U>
where
U: AsRef<str>,
{
impl Component for Confirm<'_> {
type Msg = ConfirmMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -207,7 +196,7 @@ where
fn paint(&mut self) {
self.bg.paint();
let display_top_left = |text: TString<'static>| {
let display_top_left = |text: TString| {
text.map(|t| {
display::text_top_left(Point::zero(), t, Font::BOLD, WHITE, self.bg_color)
});
@ -234,10 +223,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<U> crate::trace::Trace for Confirm<U>
where
U: AsRef<str>,
{
impl crate::trace::Trace for Confirm<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("BlConfirm");
}

View File

@ -1,8 +1,11 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::{screen, WIDTH},
display,
geometry::{Alignment2D, Offset, Point, Rect},
use crate::{
strutil::TString,
ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::{screen, WIDTH},
display,
geometry::{Alignment2D, Offset, Point, Rect},
},
};
use super::super::{
@ -13,17 +16,17 @@ use super::super::{
const FOOTER_AREA_HEIGHT: i16 = 20;
const DIVIDER_POSITION: i16 = 43;
pub struct ErrorScreen<T> {
pub struct ErrorScreen<'a> {
bg: Pad,
show_icons: bool,
title: Child<Label<T>>,
message: Child<Label<T>>,
footer: Child<Label<T>>,
title: Child<Label<'a>>,
message: Child<Label<'a>>,
footer: Child<Label<'a>>,
area: Rect,
}
impl<T: AsRef<str>> ErrorScreen<T> {
pub fn new(title: T, message: T, footer: T) -> Self {
impl<'a> ErrorScreen<'a> {
pub fn new(title: TString<'a>, message: TString<'a>, footer: TString<'a>) -> Self {
let title = Label::centered(title, theme::TEXT_BOLD);
let message = Label::centered(message, theme::TEXT_NORMAL).vertically_centered();
let footer = Label::centered(footer, theme::TEXT_NORMAL).vertically_centered();
@ -39,7 +42,7 @@ impl<T: AsRef<str>> ErrorScreen<T> {
}
}
impl<T: AsRef<str>> Component for ErrorScreen<T> {
impl Component for ErrorScreen<'_> {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {

View File

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, Paginate},
geometry::Rect,
@ -11,17 +11,16 @@ use super::{
ButtonControllerMsg, ButtonLayout, ButtonPos, CancelInfoConfirmMsg, FlowPages, Page, ScrollBar,
};
pub struct Flow<F, T>
pub struct Flow<F>
where
F: Fn(usize) -> Page<T>,
T: StringType + Clone,
F: Fn(usize) -> Page,
{
/// Function to get pages from
pages: FlowPages<F, T>,
pages: FlowPages<F>,
/// Instance of the current Page
current_page: Page<T>,
current_page: Page,
/// Title being shown at the top in bold
title: Option<Title<T>>,
title: Option<Title>,
scrollbar: Child<ScrollBar>,
content_area: Rect,
title_area: Rect,
@ -35,12 +34,11 @@ where
ignore_second_button_ms: Option<u32>,
}
impl<F, T> Flow<F, T>
impl<F> Flow<F>
where
F: Fn(usize) -> Page<T>,
T: StringType + Clone,
F: Fn(usize) -> Page,
{
pub fn new(pages: FlowPages<F, T>) -> Self {
pub fn new(pages: FlowPages<F>) -> Self {
let current_page = pages.get(0);
let title = current_page.title().map(Title::new);
Self {
@ -64,7 +62,7 @@ where
/// Adding a common title to all pages. The title will not be colliding
/// with the page content, as the content will be offset.
pub fn with_common_title(mut self, title: T) -> Self {
pub fn with_common_title(mut self, title: TString<'static>) -> Self {
self.title = Some(Title::new(title));
self
}
@ -196,10 +194,9 @@ where
}
}
impl<F, T> Component for Flow<F, T>
impl<F> Component for Flow<F>
where
F: Fn(usize) -> Page<T>,
T: StringType + Clone,
F: Fn(usize) -> Page,
{
type Msg = CancelInfoConfirmMsg;
@ -313,10 +310,9 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<F, T> crate::trace::Trace for Flow<F, T>
impl<F> crate::trace::Trace for Flow<F>
where
F: Fn(usize) -> Page<T>,
T: StringType + Clone,
F: Fn(usize) -> Page,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Flow");

View File

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{base::Component, FormattedText, Paginate},
geometry::Rect,
@ -21,10 +21,9 @@ const MAX_OPS_PER_PAGE: usize = 15;
/// have theoretically unlimited number of pages without triggering SO.
/// (Currently only the current page is stored on stack - in
/// `Flow::current_page`.)
pub struct FlowPages<F, T>
pub struct FlowPages<F>
where
T: StringType + Clone,
F: Fn(usize) -> Page<T>,
F: Fn(usize) -> Page,
{
/// Function/closure that will return appropriate page on demand.
get_page: F,
@ -32,10 +31,9 @@ where
page_count: usize,
}
impl<F, T> FlowPages<F, T>
impl<F> FlowPages<F>
where
F: Fn(usize) -> Page<T>,
T: StringType + Clone,
F: Fn(usize) -> Page,
{
pub fn new(get_page: F, page_count: usize) -> Self {
Self {
@ -45,7 +43,7 @@ where
}
/// Returns a page on demand on a specified index.
pub fn get(&self, page_index: usize) -> Page<T> {
pub fn get(&self, page_index: usize) -> Page {
(self.get_page)(page_index)
}
@ -74,24 +72,18 @@ where
}
#[derive(Clone)]
pub struct Page<T>
where
T: StringType + Clone,
{
pub struct Page {
formatted: FormattedText,
btn_layout: ButtonLayout,
btn_actions: ButtonActions,
current_page: usize,
page_count: usize,
title: Option<T>,
title: Option<TString<'static>>,
slim_arrows: bool,
}
// For `layout.rs`
impl<T> Page<T>
where
T: StringType + Clone,
{
impl Page {
pub fn new(
btn_layout: ButtonLayout,
btn_actions: ButtonActions,
@ -112,12 +104,9 @@ where
}
// For `flow.rs`
impl<T> Page<T>
where
T: StringType + Clone,
{
impl Page {
/// Adding title.
pub fn with_title(mut self, title: T) -> Self {
pub fn with_title(mut self, title: TString<'static>) -> Self {
self.title = Some(title);
self
}
@ -180,8 +169,8 @@ where
self.btn_actions
}
pub fn title(&self) -> Option<T> {
self.title.clone()
pub fn title(&self) -> Option<TString<'static>> {
self.title
}
pub fn has_prev_page(&self) -> bool {
@ -208,10 +197,7 @@ where
}
// Pagination
impl<T> Paginate for Page<T>
where
T: StringType + Clone,
{
impl Paginate for Page {
fn page_count(&mut self) -> usize {
self.formatted.page_count()
}
@ -227,10 +213,7 @@ where
use crate::ui::component::text::layout::LayoutFit;
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Page<T>
where
T: StringType + Clone,
{
impl crate::trace::Trace for Page {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
use crate::ui::component::text::layout::trace::TraceSink;
use core::cell::Cell;
@ -238,7 +221,7 @@ where
t.component("Page");
if let Some(title) = &self.title {
// Not calling it "title" as that is already traced by FlowPage
t.string("page_title", title.as_ref().into());
t.string("page_title", *title);
}
t.int("active_page", self.current_page as i64);
t.int("page_count", self.page_count as i64);

View File

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Paginate},
geometry::{Insets, Rect},
@ -9,21 +9,19 @@ use crate::{
use super::{super::constant, scrollbar::SCROLLBAR_SPACE, theme, title::Title, ScrollBar};
/// Component for holding another component and displaying a title.
pub struct Frame<T, U>
pub struct Frame<T>
where
T: Component,
U: StringType,
{
title: Title<U>,
title: Title,
content: Child<T>,
}
impl<T, U> Frame<T, U>
impl<T> Frame<T>
where
T: Component,
U: StringType + Clone,
{
pub fn new(title: U, content: T) -> Self {
pub fn new(title: TString<'static>, content: T) -> Self {
Self {
title: Title::new(title),
content: Child::new(content),
@ -40,7 +38,7 @@ where
self.content.inner()
}
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: U) {
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: TString<'static>) {
self.title.set_text(ctx, new_title);
}
@ -56,10 +54,9 @@ where
}
}
impl<T, U> Component for Frame<T, U>
impl<T> Component for Frame<T>
where
T: Component,
U: StringType + Clone,
{
type Msg = T::Msg;
@ -85,10 +82,9 @@ where
}
}
impl<T, U> Paginate for Frame<T, U>
impl<T> Paginate for Frame<T>
where
T: Component + Paginate,
U: StringType + Clone,
{
fn page_count(&mut self) -> usize {
self.content.page_count()
@ -106,20 +102,18 @@ pub trait ScrollableContent {
/// Component for holding another component and displaying a title.
/// Also is allocating space for a scrollbar.
pub struct ScrollableFrame<T, U>
pub struct ScrollableFrame<T>
where
T: Component + ScrollableContent,
U: StringType + Clone,
{
title: Option<Child<Title<U>>>,
title: Option<Child<Title>>,
scrollbar: ScrollBar,
content: Child<T>,
}
impl<T, U> ScrollableFrame<T, U>
impl<T> ScrollableFrame<T>
where
T: Component + ScrollableContent,
U: StringType + Clone,
{
pub fn new(content: T) -> Self {
Self {
@ -133,16 +127,15 @@ where
self.content.inner()
}
pub fn with_title(mut self, title: U) -> Self {
pub fn with_title(mut self, title: TString<'static>) -> Self {
self.title = Some(Child::new(Title::new(title)));
self
}
}
impl<T, U> Component for ScrollableFrame<T, U>
impl<T> Component for ScrollableFrame<T>
where
T: Component + ScrollableContent,
U: StringType + Clone,
{
type Msg = T::Msg;
@ -209,10 +202,9 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Frame<T, U>
impl<T> crate::trace::Trace for Frame<T>
where
T: crate::trace::Trace + Component,
U: StringType + Clone,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Frame");
@ -222,10 +214,9 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for ScrollableFrame<T, U>
impl<T> crate::trace::Trace for ScrollableFrame<T>
where
T: crate::trace::Trace + Component + ScrollableContent,
U: StringType + Clone,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ScrollableFrame");

View File

@ -1,7 +1,5 @@
use crate::{
error::Error,
micropython::buffer::StrBuffer,
strutil::StringType,
strutil::TString,
translations::TR,
trezorhal::usb::usb_configured,
ui::{
@ -54,29 +52,27 @@ enum CurrentScreen {
Loader,
}
pub struct Homescreen<T>
where
T: StringType,
{
pub struct Homescreen {
// TODO label should be a Child in theory, but the homescreen image is not, so it is
// always painted, so we need to always paint the label too
label: Label<T>,
notification: Option<(T, u8)>,
label: Label<'static>,
notification: Option<(TString<'static>, u8)>,
/// Used for HTC functionality to lock device from homescreen
invisible_buttons: Child<ButtonController>,
/// Holds the loader component
loader: Option<Child<ProgressLoader<T>>>,
loader: Option<Child<ProgressLoader>>,
/// Whether to show the loader or not
show_loader: bool,
/// Which screen is currently shown
current_screen: CurrentScreen,
}
impl<T> Homescreen<T>
where
T: StringType + Clone,
{
pub fn new(label: T, notification: Option<(T, u8)>, loader_description: Option<T>) -> Self {
impl Homescreen {
pub fn new(
label: TString<'static>,
notification: Option<(TString<'static>, u8)>,
loader_description: Option<TString<'static>>,
) -> Self {
// Buttons will not be visible, we only need both left and right to be existing
// so we can get the events from them.
let invisible_btn_layout = ButtonLayout::text_none_text("".into(), "".into());
@ -114,11 +110,11 @@ where
.map_translated(|t| display_center(baseline, t, NOTIFICATION_FONT));
} else if let Some((notification, _level)) = &self.notification {
self.fill_notification_background();
display_center(baseline, notification.as_ref(), NOTIFICATION_FONT);
notification.map(|c| display_center(baseline, c, 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());
let text_width = notification.map(|c| NOTIFICATION_FONT.text_width(c));
if AREA.width() >= text_width + (icon_width + 1) * 2 {
NOTIFICATION_ICON.draw(
AREA.top_left(),
@ -169,10 +165,7 @@ where
}
}
impl<T> Component for Homescreen<T>
where
T: StringType + Clone,
{
impl Component for Homescreen {
type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect {
@ -238,12 +231,9 @@ where
}
}
pub struct Lockscreen<T>
where
T: StringType,
{
label: Child<Label<T>>,
instruction: Child<Label<StrBuffer>>,
pub struct Lockscreen<'a> {
label: Child<Label<'a>>,
instruction: Child<Label<'static>>,
/// Used for unlocking the device from lockscreen
invisible_buttons: Child<ButtonController>,
/// Display coinjoin icon?
@ -252,11 +242,8 @@ where
screensaver: bool,
}
impl<T> Lockscreen<T>
where
T: StringType + Clone,
{
pub fn new(label: T, bootscreen: bool, coinjoin_authorized: bool) -> Result<Self, Error> {
impl<'a> Lockscreen<'a> {
pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
// Buttons will not be visible, we only need all three of them to be present,
// so that even middle-click triggers the event.
let invisible_btn_layout = ButtonLayout::arrow_armed_arrow("".into());
@ -265,23 +252,17 @@ where
} else {
TR::homescreen__click_to_unlock
};
Ok(Lockscreen {
Self {
label: Child::new(Label::centered(label, theme::TEXT_BIG)),
instruction: Child::new(Label::centered(
instruction_str.try_into()?,
theme::TEXT_NORMAL,
)),
instruction: Child::new(Label::centered(instruction_str.into(), theme::TEXT_NORMAL)),
invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)),
coinjoin_icon: coinjoin_authorized.then_some(theme::ICON_COINJOIN),
screensaver: !bootscreen,
})
}
}
}
impl<T> Component for Lockscreen<T>
where
T: StringType + Clone,
{
impl Component for Lockscreen<'_> {
type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect {
@ -322,20 +303,14 @@ where
}
}
pub struct ConfirmHomescreen<T, F>
where
T: StringType,
{
title: Child<Label<T>>,
pub struct ConfirmHomescreen<F> {
title: Child<Label<'static>>,
buffer_func: F,
buttons: Child<ButtonController>,
}
impl<T, F> ConfirmHomescreen<T, F>
where
T: StringType + Clone,
{
pub fn new(title: T, buffer_func: F) -> Self {
impl<F> ConfirmHomescreen<F> {
pub fn new(title: TString<'static>, buffer_func: F) -> Self {
let btn_layout = ButtonLayout::cancel_none_text(TR::buttons__change.into());
ConfirmHomescreen {
title: Child::new(Label::centered(title, theme::TEXT_BOLD)),
@ -345,9 +320,8 @@ where
}
}
impl<'a, T, F> Component for ConfirmHomescreen<T, F>
impl<'a, F> Component for ConfirmHomescreen<F>
where
T: StringType + Clone,
F: Fn() -> &'a [u8],
{
type Msg = CancelConfirmMsg;
@ -398,10 +372,7 @@ pub fn check_homescreen_format(toif: &Toif) -> bool {
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Homescreen<T>
where
T: StringType,
{
impl crate::trace::Trace for Homescreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Homescreen");
t.child("label", &self.label);
@ -409,10 +380,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Lockscreen<T>
where
T: StringType,
{
impl crate::trace::Trace for Lockscreen<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Lockscreen");
t.child("label", &self.label);
@ -420,10 +388,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T, F> crate::trace::Trace for ConfirmHomescreen<T, F>
where
T: StringType,
{
impl<F> crate::trace::Trace for ConfirmHomescreen<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ConfirmHomescreen");
t.child("title", &self.title);

View File

@ -1,5 +1,5 @@
use crate::{
strutil::{StringType, TString},
strutil::TString,
translations::TR,
trezorhal::random,
ui::{
@ -129,12 +129,12 @@ impl ChoiceFactory for ChoiceFactoryPIN {
}
/// Component for entering a PIN.
pub struct PinEntry<T: StringType + Clone> {
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>>>,
prompt: T,
subprompt: T,
prompt: TString<'a>,
subprompt: TString<'a>,
/// Whether we already show the "real" prompt (not the warning).
showing_real_prompt: bool,
show_real_pin: bool,
@ -142,26 +142,23 @@ pub struct PinEntry<T: StringType + Clone> {
textbox: TextBox<MAX_PIN_LENGTH>,
}
impl<T> PinEntry<T>
where
T: StringType + Clone,
{
pub fn new(prompt: T, subprompt: T) -> Self {
impl<'a> PinEntry<'a> {
pub fn new(prompt: TString<'a>, subprompt: TString<'a>) -> Self {
// When subprompt is not empty, it means that the user has entered bad PIN
// before. In this case we show the warning together with the subprompt
// at the beginning. (WRONG PIN will be replaced by real prompt after
// any button click.)
let show_subprompt = !subprompt.as_ref().is_empty();
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))),
unwrap!(String::try_from(subprompt.as_ref())),
subprompt.map(|s| unwrap!(String::try_from(s))),
)
} else {
(
true,
unwrap!(String::try_from(prompt.as_ref())),
prompt.map(|s| unwrap!(String::try_from(s))),
unwrap!(String::try_from(EMPTY_PIN_STR)),
)
};
@ -201,10 +198,10 @@ where
/// Many possibilities, according to the PIN state.
fn update_pin_line(&mut self, ctx: &mut EventCtx) {
let mut used_font = Font::BOLD;
let pin_line_text = if self.is_empty() && !self.subprompt.as_ref().is_empty() {
let pin_line_text = if self.is_empty() && !self.subprompt.is_empty() {
// Showing the subprompt in NORMAL font
used_font = Font::NORMAL;
unwrap!(String::try_from(self.subprompt.as_ref()))
self.subprompt.map(|s| unwrap!(String::try_from(s)))
} else if self.is_empty() {
unwrap!(String::try_from(EMPTY_PIN_STR))
} else if self.show_real_pin {
@ -234,7 +231,7 @@ where
/// 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(unwrap!(String::try_from(self.prompt.as_ref())));
header_line.update_text(self.prompt.map(|s| unwrap!(String::try_from(s))));
header_line.request_complete_repaint(ctx);
});
}
@ -252,10 +249,7 @@ where
}
}
impl<T> Component for PinEntry<T>
where
T: StringType + Clone,
{
impl Component for PinEntry<'_> {
type Msg = CancelConfirmMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -334,13 +328,10 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for PinEntry<T>
where
T: StringType + Clone,
{
impl crate::trace::Trace for PinEntry<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("PinKeyboard");
t.string("subprompt", self.subprompt.as_ref().into());
t.string("subprompt", self.subprompt);
t.string("pin", self.textbox.content().into());
t.child("choice_page", &self.choice_page);
}

View File

@ -1,5 +1,5 @@
use crate::{
strutil::{StringType, TString},
strutil::TString,
time::{Duration, Instant},
ui::{
animation::Animation,
@ -247,22 +247,16 @@ impl LoaderStyleSheet {
}
}
pub struct ProgressLoader<T>
where
T: StringType,
{
loader: Child<Progress<T>>,
pub struct ProgressLoader {
loader: Child<Progress>,
duration_ms: u32,
start_time: Option<Instant>,
}
impl<T> ProgressLoader<T>
where
T: StringType + Clone,
{
impl ProgressLoader {
const LOADER_FRAMES_DEFAULT: u32 = 20;
pub fn new(loader_description: T, duration_ms: u32) -> Self {
pub fn new(loader_description: TString<'static>, duration_ms: u32) -> Self {
Self {
loader: Child::new(
Progress::new(false, loader_description).with_icon(theme::ICON_LOCK_SMALL),
@ -300,10 +294,7 @@ where
}
}
impl<T> Component for ProgressLoader<T>
where
T: StringType + Clone,
{
impl Component for ProgressLoader {
type Msg = LoaderMsg;
fn place(&mut self, bounds: Rect) -> Rect {

View File

@ -1,7 +1,7 @@
use core::mem;
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{
paginated::Paginate,
@ -21,11 +21,8 @@ const BOTTOM_DESCRIPTION_MARGIN: i16 = 10;
const LOADER_Y_OFFSET_TITLE: i16 = -10;
const LOADER_Y_OFFSET_NO_TITLE: i16 = -20;
pub struct Progress<T>
where
T: StringType,
{
title: Option<Child<Label<T>>>,
pub struct Progress {
title: Option<Child<Label<'static>>>,
value: u16,
loader_y_offset: i16,
indeterminate: bool,
@ -34,13 +31,10 @@ where
icon: Icon,
}
impl<T> Progress<T>
where
T: StringType,
{
impl Progress {
const AREA: Rect = constant::screen();
pub fn new(indeterminate: bool, description: T) -> Self {
pub fn new(indeterminate: bool, description: TString<'static>) -> Self {
Self {
title: None,
value: 0,
@ -54,7 +48,7 @@ where
}
}
pub fn with_title(mut self, title: T) -> Self {
pub fn with_title(mut self, title: TString<'static>) -> Self {
self.title = Some(Child::new(Label::centered(title, theme::TEXT_BOLD)));
self
}
@ -79,10 +73,7 @@ where
}
}
impl<T> Component for Progress<T>
where
T: StringType,
{
impl Component for Progress {
type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect {
@ -95,7 +86,7 @@ where
let no_title_case = (Rect::zero(), Self::AREA, LOADER_Y_OFFSET_NO_TITLE);
let (title, rest, loader_y_offset) = if let Some(self_title) = &self.title {
if !self_title.inner().text().as_ref().is_empty() {
if !self_title.inner().text().is_empty() {
let (title, rest) = Self::AREA.split_top(self_title.inner().max_size().y);
(title, rest, LOADER_Y_OFFSET_TITLE)
} else {
@ -171,10 +162,7 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Progress<T>
where
T: StringType,
{
impl crate::trace::Trace for Progress {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Progress");
}

View File

@ -17,8 +17,8 @@ pub struct ResultScreen<'a> {
fg_color: Color,
bg_color: Color,
icon: Icon,
message_top: Child<Label<&'static str>>,
message_bottom: Child<Label<&'a str>>,
message_top: Child<Label<'static>>,
message_bottom: Child<Label<'a>>,
}
impl<'a> ResultScreen<'a> {
@ -26,8 +26,8 @@ impl<'a> ResultScreen<'a> {
fg_color: Color,
bg_color: Color,
icon: Icon,
title: Label<&'static str>,
content: Label<&'a str>,
title: Label<'static>,
content: Label<'a>,
complete_draw: bool,
) -> Self {
let mut instance = Self {
@ -49,7 +49,7 @@ impl<'a> ResultScreen<'a> {
}
}
impl<'a> Component for ResultScreen<'a> {
impl Component for ResultScreen<'_> {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {

View File

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
time::Instant,
ui::{
component::{Component, Event, EventCtx, Marquee, Never},
@ -10,24 +10,18 @@ use crate::{
use super::super::theme;
pub struct Title<T>
where
T: StringType,
{
pub struct Title {
area: Rect,
title: T,
marquee: Marquee<T>,
title: TString<'static>,
marquee: Marquee,
needs_marquee: bool,
centered: bool,
}
impl<T> Title<T>
where
T: StringType + Clone,
{
pub fn new(title: T) -> Self {
impl Title {
pub fn new(title: TString<'static>) -> Self {
Self {
title: title.clone(),
title,
marquee: Marquee::new(title, theme::FONT_HEADER, theme::FG, theme::BG),
needs_marquee: false,
area: Rect::zero(),
@ -40,14 +34,14 @@ where
self
}
pub fn get_text(&self) -> &str {
self.title.as_ref()
pub fn get_text(&self) -> TString {
self.title
}
pub fn set_text(&mut self, ctx: &mut EventCtx, new_text: T) {
self.title = new_text.clone();
self.marquee.set_text(new_text.clone());
let text_width = theme::FONT_HEADER.text_width(new_text.as_ref());
pub fn set_text(&mut self, ctx: &mut EventCtx, new_text: TString<'static>) {
self.title = new_text;
self.marquee.set_text(new_text);
let text_width = new_text.map(|s| theme::FONT_HEADER.text_width(s));
self.needs_marquee = text_width > self.area.width();
// Resetting the marquee to the beginning and starting it when necessary.
self.marquee.reset();
@ -57,42 +51,31 @@ where
}
/// Display title/header at the top left of the given area.
pub fn paint_header_left(title: &T, area: Rect) {
pub fn paint_header_left(title: &TString<'static>, area: Rect) {
let text_height = theme::FONT_HEADER.text_height();
let title_baseline = area.top_left() + Offset::y(text_height - 1);
display::text_left(
title_baseline,
title.as_ref(),
theme::FONT_HEADER,
theme::FG,
theme::BG,
);
title.map(|s| {
display::text_left(title_baseline, s, theme::FONT_HEADER, theme::FG, theme::BG)
});
}
/// Display title/header centered at the top of the given area.
pub fn paint_header_centered(title: &T, area: Rect) {
pub fn paint_header_centered(title: &TString<'static>, area: Rect) {
let text_height = theme::FONT_HEADER.text_height();
let title_baseline = area.top_center() + Offset::y(text_height - 1);
display::text_center(
title_baseline,
title.as_ref(),
theme::FONT_HEADER,
theme::FG,
theme::BG,
);
title.map(|s| {
display::text_center(title_baseline, s, theme::FONT_HEADER, theme::FG, theme::BG)
});
}
}
impl<T> Component for Title<T>
where
T: StringType + Clone,
{
impl Component for Title {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.marquee.place(bounds);
let width = theme::FONT_HEADER.text_width(self.title.as_ref());
let width = self.title.map(|s| theme::FONT_HEADER.text_width(s));
self.needs_marquee = width > self.area.width();
bounds
}
@ -121,12 +104,9 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Title<T>
where
T: StringType + Clone,
{
impl crate::trace::Trace for Title {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Title");
t.string("text", self.title.as_ref().into());
t.string("text", self.title);
}
}

View File

@ -99,10 +99,9 @@ where
}
}
impl<F, T> ComponentMsgObj for Flow<F, T>
impl<F> ComponentMsgObj for Flow<F>
where
F: Fn(usize) -> Page<T>,
T: StringType + Clone,
F: Fn(usize) -> Page,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
@ -119,10 +118,7 @@ where
}
}
impl<T> ComponentMsgObj for PinEntry<T>
where
T: StringType + Clone,
{
impl ComponentMsgObj for PinEntry<'_> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
CancelConfirmMsg::Confirmed => self.pin().try_into(),
@ -187,56 +183,44 @@ impl ComponentMsgObj for PassphraseEntry {
}
}
impl<T, U> ComponentMsgObj for Frame<T, U>
impl<T> ComponentMsgObj for Frame<T>
where
T: ComponentMsgObj,
U: StringType + Clone,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
self.inner().msg_try_into_obj(msg)
}
}
impl<T, U> ComponentMsgObj for ScrollableFrame<T, U>
impl<T> ComponentMsgObj for ScrollableFrame<T>
where
T: ComponentMsgObj + ScrollableContent,
U: StringType + Clone,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
self.inner().msg_try_into_obj(msg)
}
}
impl<T> ComponentMsgObj for Progress<T>
where
T: StringType,
{
impl ComponentMsgObj for Progress {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!()
}
}
impl<T> ComponentMsgObj for Homescreen<T>
where
T: StringType + Clone,
{
impl ComponentMsgObj for Homescreen {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
Ok(CANCELLED.as_obj())
}
}
impl<T> ComponentMsgObj for Lockscreen<T>
where
T: StringType + Clone,
{
impl<'a> ComponentMsgObj for Lockscreen<'a> {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
Ok(CANCELLED.as_obj())
}
}
impl<'a, T, F> ComponentMsgObj for ConfirmHomescreen<T, F>
impl<'a, F> ComponentMsgObj for ConfirmHomescreen<F>
where
T: StringType + Clone,
F: Fn() -> &'a [u8],
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
@ -247,10 +231,7 @@ where
}
}
impl<U> ComponentMsgObj for super::component::bl_confirm::Confirm<U>
where
U: AsRef<str>,
{
impl ComponentMsgObj for super::component::bl_confirm::Confirm<'_> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
super::component::bl_confirm::ConfirmMsg::Cancel => Ok(CANCELLED.as_obj()),
@ -290,7 +271,7 @@ fn content_in_button_page<T: Component + Paginate + MaybeTrace + 'static>(
let mut frame = ScrollableFrame::new(content);
if !title.as_ref().is_empty() {
frame = frame.with_title(title);
frame = frame.with_title(title.into());
}
let obj = LayoutObj::new(frame)?;
@ -430,7 +411,7 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
// discarded before returning to micropython.
let buffer_func = move || unsafe { unwrap!(get_buffer(data)) };
let obj = LayoutObj::new(ConfirmHomescreen::new(title, buffer_func))?;
let obj = LayoutObj::new(ConfirmHomescreen::new(title.into(), buffer_func))?;
Ok(obj.into())
};
@ -457,10 +438,6 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
extern "C" fn new_confirm_backup(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = move |_args: &[Obj], _kwargs: &Map| {
// cached allocated translations that get_page can reuse
let tr_title_success: StrBuffer = TR::words__title_success.try_into()?;
let tr_title_backup_wallet: StrBuffer = TR::backup__title_backup_wallet.try_into()?;
let get_page = move |page_index| match page_index {
0 => {
let btn_layout = ButtonLayout::text_none_arrow_wide(TR::buttons__skip.into());
@ -470,7 +447,8 @@ extern "C" fn new_confirm_backup(n_args: usize, args: *const Obj, kwargs: *mut M
.newline()
.text_normal(TR::backup__it_should_be_backed_up_now);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(tr_title_success)
Page::new(btn_layout, btn_actions, formatted)
.with_title(TR::words__title_success.into())
}
1 => {
let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__back_up.into());
@ -478,8 +456,8 @@ extern "C" fn new_confirm_backup(n_args: usize, args: *const Obj, kwargs: *mut M
let ops =
OpTextLayout::new(theme::TEXT_NORMAL).text_normal(TR::backup__recover_anytime);
let formatted = FormattedText::new(ops).vertically_centered();
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
.with_title(tr_title_backup_wallet)
Page::new(btn_layout, btn_actions, formatted)
.with_title(TR::backup__title_backup_wallet.into())
}
_ => unreachable!(),
};
@ -624,7 +602,7 @@ extern "C" fn new_confirm_output_address(n_args: usize, args: *const Obj, kwargs
}
ops = ops.text_mono(address);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(address_title)
Page::new(btn_layout, btn_actions, formatted).with_title(address_title.into())
};
let pages = FlowPages::new(get_page, 1);
@ -646,7 +624,7 @@ extern "C" fn new_confirm_output_amount(n_args: usize, args: *const Obj, kwargs:
let btn_actions = ButtonActions::cancel_none_confirm();
let ops = OpTextLayout::new(theme::TEXT_MONO).text_mono(amount);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(amount_title)
Page::new(btn_layout, btn_actions, formatted).with_title(amount_title.into())
};
let pages = FlowPages::new(get_page, 1);
@ -686,7 +664,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
.text_mono(fee_amount);
let formatted = FormattedText::new(ops);
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
Page::new(btn_layout, btn_actions, formatted)
}
1 => {
// Fee rate info
@ -748,9 +726,6 @@ extern "C" fn new_altcoin_tx_summary(n_args: usize, args: *const Obj, kwargs: *m
let cancel_cross: bool = kwargs.get_or(Qstr::MP_QSTR_cancel_cross, false)?;
let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?;
// cached allocated translated strings that get_page can reuse
let tr_title_fee = TR::confirm_total__title_fee.try_into()?;
let get_page = move |page_index| {
match page_index {
0 => {
@ -771,7 +746,7 @@ extern "C" fn new_altcoin_tx_summary(n_args: usize, args: *const Obj, kwargs: *m
.text_mono(fee_value);
let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted).with_title(amount_title)
Page::new(btn_layout, btn_actions, formatted).with_title(amount_title.into())
}
1 => {
// Other information
@ -794,7 +769,7 @@ extern "C" fn new_altcoin_tx_summary(n_args: usize, args: *const Obj, kwargs: *m
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted)
.with_title(tr_title_fee)
.with_title(TR::confirm_total__title_fee.into())
.with_slim_arrows()
}
_ => unreachable!(),
@ -829,7 +804,7 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
};
let ops = OpTextLayout::new(style).text_mono(address);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(title)
Page::new(btn_layout, btn_actions, formatted).with_title(title.into())
};
let pages = FlowPages::new(get_page, 1);
@ -842,11 +817,11 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
/// General pattern of most tutorial screens.
/// (title, text, btn_layout, btn_actions, text_y_offset)
fn tutorial_screen(
title: StrBuffer,
title: TString<'static>,
text: TR,
btn_layout: ButtonLayout,
btn_actions: ButtonActions,
) -> Page<StrBuffer> {
) -> Page {
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted).with_title(title)
@ -856,15 +831,6 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: usize = 7;
// cached allocated translated strings that get_page can reuse
let tr_title_hello: StrBuffer = TR::tutorial__title_hello.try_into()?;
let tr_hold_to_confirm: StrBuffer = TR::buttons__hold_to_confirm.try_into()?;
let tr_title_screen_scroll: StrBuffer = TR::tutorial__title_screen_scroll.try_into()?;
let tr_confirm: StrBuffer = TR::buttons__confirm.try_into()?;
let tr_title_tutorial_complete: StrBuffer =
TR::tutorial__title_tutorial_complete.try_into()?;
let tr_title_skip: StrBuffer = TR::tutorial__title_skip.try_into()?;
let get_page = move |page_index| {
// Lazy-loaded list of screens to show, with custom content,
// buttons and actions triggered by these buttons.
@ -874,7 +840,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
match page_index {
// title, text, btn_layout, btn_actions
0 => tutorial_screen(
tr_title_hello,
TR::tutorial__title_hello.into(),
TR::tutorial__welcome_press_right,
ButtonLayout::cancel_none_arrow(),
ButtonActions::last_none_next(),
@ -886,25 +852,25 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
ButtonActions::prev_none_next(),
),
2 => tutorial_screen(
tr_hold_to_confirm,
TR::buttons__hold_to_confirm.into(),
TR::tutorial__press_and_hold,
ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()),
ButtonActions::prev_none_next(),
),
3 => tutorial_screen(
tr_title_screen_scroll,
TR::tutorial__title_screen_scroll.into(),
TR::tutorial__scroll_down,
ButtonLayout::arrow_none_text(TR::buttons__continue.into()),
ButtonActions::prev_none_next(),
),
4 => tutorial_screen(
tr_confirm,
TR::buttons__confirm.into(),
TR::tutorial__middle_click,
ButtonLayout::none_armed_none(TR::buttons__confirm.into()),
ButtonActions::none_next_none(),
),
5 => tutorial_screen(
tr_title_tutorial_complete,
TR::tutorial__title_tutorial_complete.into(),
TR::tutorial__ready_to_use,
ButtonLayout::text_none_text(
TR::buttons__again.into(),
@ -913,7 +879,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj
ButtonActions::beginning_none_confirm(),
),
6 => tutorial_screen(
tr_title_skip,
TR::tutorial__title_skip.into(),
TR::tutorial__sure_you_want_skip,
ButtonLayout::arrow_none_text(TR::buttons__skip.into()),
ButtonActions::beginning_none_cancel(),
@ -1021,11 +987,11 @@ extern "C" fn new_multiple_pages_texts(n_args: usize, args: *const Obj, kwargs:
let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_centered();
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, page_count);
let obj = LayoutObj::new(Flow::new(pages).with_common_title(title))?;
let obj = LayoutObj::new(Flow::new(pages).with_common_title(title.into()))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1080,14 +1046,14 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
.text_bold(account);
let formatted = FormattedText::new(ops);
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, page_count);
// Returning the page index in case of confirmation.
let obj = LayoutObj::new(
Flow::new(pages)
.with_common_title(title)
.with_common_title(title.into())
.with_return_confirmed_index(),
)?;
Ok(obj.into())
@ -1115,7 +1081,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map
ops = ops.text_normal(description);
}
let formatted = FormattedText::new(ops).vertically_centered();
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, 1);
let obj = LayoutObj::new(Flow::new(pages))?;
@ -1132,7 +1098,7 @@ extern "C" fn new_show_info(n_args: usize, args: *const Obj, kwargs: *mut Map) -
let time_ms: u32 = kwargs.get_or(Qstr::MP_QSTR_time_ms, 0)?;
let content = Frame::new(
title,
title.into(),
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]),
);
let obj = if time_ms == 0 {
@ -1189,7 +1155,7 @@ extern "C" fn new_show_mismatch(n_args: usize, args: *const Obj, kwargs: *mut Ma
.newline()
.text_bold(TR::addr_mismatch__support_url);
let formatted = FormattedText::new(ops);
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
Page::new(btn_layout, btn_actions, formatted)
};
let pages = FlowPages::new(get_page, 1);
@ -1222,7 +1188,7 @@ extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mu
}
let obj = LayoutObj::new(Frame::new(
title,
title.into(),
ShowMore::<Paragraphs<ParagraphVecShort>>::new(
paragraphs.into_paragraphs(),
verb_cancel,
@ -1291,7 +1257,7 @@ extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map)
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
let obj = LayoutObj::new(PinEntry::new(prompt, subprompt))?;
let obj = LayoutObj::new(PinEntry::new(prompt.into(), subprompt.into()))?;
Ok(obj.into())
};
@ -1302,7 +1268,9 @@ extern "C" fn new_request_passphrase(n_args: usize, args: *const Obj, kwargs: *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, PassphraseEntry::new()).with_title_centered())?;
let obj = LayoutObj::new(
Frame::new(prompt.into(), PassphraseEntry::new()).with_title_centered(),
)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1310,13 +1278,13 @@ extern "C" fn new_request_passphrase(n_args: usize, args: *const Obj, kwargs: *m
extern "C" fn new_request_bip39(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], kwargs: &Map| {
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let prompt: TString = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
let prefill_word: StrBuffer = kwargs.get(Qstr::MP_QSTR_prefill_word)?.try_into()?;
let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?;
let obj = LayoutObj::new(
Frame::new(
prompt,
prompt.into(),
WordlistEntry::prefilled_word(
prefill_word.as_ref(),
WordlistType::Bip39,
@ -1338,7 +1306,7 @@ extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut M
let obj = LayoutObj::new(
Frame::new(
prompt,
prompt.into(),
WordlistEntry::prefilled_word(
prefill_word.as_ref(),
WordlistType::Slip39,
@ -1363,7 +1331,7 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut Map)
// Returning the index of the selected word, not the word itself
let obj = LayoutObj::new(
Frame::new(
description,
description.into(),
SimpleChoice::new(words, false)
.with_show_incomplete()
.with_return_index(),
@ -1402,7 +1370,8 @@ extern "C" fn new_request_number(n_args: usize, args: *const Obj, kwargs: *mut M
let count: u32 = kwargs.get(Qstr::MP_QSTR_count)?.try_into()?;
let obj = LayoutObj::new(
Frame::new(title, NumberInput::new(min_count, max_count, count)).with_title_centered(),
Frame::new(title.into(), NumberInput::new(min_count, max_count, count))
.with_title_centered(),
)?;
Ok(obj.into())
};
@ -1497,7 +1466,7 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu
.collect();
let obj = LayoutObj::new(
Frame::new(title, SimpleChoice::new(choices, false)).with_title_centered(),
Frame::new(title.into(), SimpleChoice::new(choices, false)).with_title_centered(),
)?;
Ok(obj.into())
};
@ -1542,9 +1511,9 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
.and_then(Obj::try_into_option)
.unwrap_or(None);
let mut progress = Progress::new(indeterminate, description);
let mut progress = Progress::new(indeterminate, description.into());
if let Some(title) = title {
progress = progress.with_title(title);
progress = progress.with_title(title.into());
};
// Description updates are received as &str and we need to provide a way to
@ -1590,9 +1559,13 @@ extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
let hold: bool = kwargs.get(Qstr::MP_QSTR_hold)?.try_into()?;
let notification = notification.map(|w| (w, notification_level));
let notification = notification.map(|w| (w.into(), notification_level));
let loader_description = hold.then_some("Locking the device...".into());
let obj = LayoutObj::new(Homescreen::new(label, notification, loader_description))?;
let obj = LayoutObj::new(Homescreen::new(
label.into(),
notification,
loader_description,
))?;
if skip_first_paint {
obj.skip_first_paint();
}
@ -1611,7 +1584,11 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut
let coinjoin_authorized: bool = kwargs.get_or(Qstr::MP_QSTR_coinjoin_authorized, false)?;
let skip_first_paint: bool = kwargs.get(Qstr::MP_QSTR_skip_first_paint)?.try_into()?;
let obj = LayoutObj::new(Lockscreen::new(label, bootscreen, coinjoin_authorized)?)?;
let obj = LayoutObj::new(Lockscreen::new(
label.into(),
bootscreen,
coinjoin_authorized,
))?;
if skip_first_paint {
obj.skip_first_paint();
}
@ -1631,16 +1608,27 @@ extern "C" fn new_confirm_firmware_update(
let fingerprint: StrBuffer = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?;
let title = TR::firmware_update__title;
let message = Label::left_aligned(description, theme::TEXT_NORMAL).vertically_centered();
let message =
Label::left_aligned(description.into(), theme::TEXT_NORMAL).vertically_centered();
let fingerprint = Label::left_aligned(
fingerprint,
fingerprint.into(),
theme::TEXT_NORMAL.with_line_breaking(LineBreaking::BreakWordsNoHyphen),
)
.vertically_centered();
let obj = LayoutObj::new(
Confirm::new(theme::BG, title, message, None, TR::buttons__install, false)
.with_info_screen(TR::firmware_update__title_fingerprint, fingerprint),
Confirm::new(
theme::BG,
title.into(),
message,
None,
TR::buttons__install.as_tstring(),
false,
)
.with_info_screen(
TR::firmware_update__title_fingerprint.as_tstring(),
fingerprint,
),
)?;
Ok(obj.into())
};

View File

@ -1,31 +1,11 @@
#[cfg(feature = "micropython")]
use crate::micropython::buffer::StrBuffer;
use crate::ui::{
component::base::Component, constant::screen, display, model_tr::component::WelcomeScreen,
};
use super::{component::ErrorScreen, constant};
#[cfg(not(feature = "micropython"))]
// SAFETY: Actually safe but see below
unsafe fn get_str(text: &str) -> &str {
text
}
#[cfg(feature = "micropython")]
// SAFETY: The caller is responsible for ensuring that the StrBuffer does not
// escape the lifetime of the original &str.
unsafe fn get_str(text: &str) -> StrBuffer {
unsafe { StrBuffer::from_ptr_and_len(text.as_ptr(), text.len()) }
}
pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
// SAFETY: these will get placed into `frame` which does not outlive this
// function
let title = unsafe { get_str(title) };
let msg = unsafe { get_str(msg) };
let footer = unsafe { get_str(footer) };
let mut frame = ErrorScreen::new(title, msg, footer);
let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into());
frame.place(constant::screen());
frame.paint();
}

View File

@ -1,14 +1,17 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
constant::screen,
display::Icon,
geometry::{Alignment, Insets, Point, Rect},
model_tt::{
component::{Button, ButtonMsg::Clicked},
constant::WIDTH,
theme::bootloader::{
button_bld, button_bld_menu, text_title, BLD_BG, BUTTON_AREA_START, BUTTON_HEIGHT,
CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_WARNING, TITLE_AREA,
use crate::{
strutil::TString,
ui::{
component::{Child, Component, Event, EventCtx, Label, Pad},
constant::screen,
display::Icon,
geometry::{Alignment, Insets, Point, Rect},
model_tt::{
component::{Button, ButtonMsg::Clicked},
constant::WIDTH,
theme::bootloader::{
button_bld, button_bld_menu, text_title, BLD_BG, BUTTON_AREA_START, BUTTON_HEIGHT,
CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_WARNING, TITLE_AREA,
},
},
},
};
@ -22,15 +25,15 @@ pub enum IntroMsg {
pub struct Intro<'a> {
bg: Pad,
title: Child<Label<&'a str>>,
menu: Child<Button<&'static str>>,
host: Child<Button<&'static str>>,
text: Child<Label<&'a str>>,
warn: Option<Child<Label<&'a str>>>,
title: Child<Label<'a>>,
menu: Child<Button>,
host: Child<Button>,
text: Child<Label<'a>>,
warn: Option<Child<Label<'a>>>,
}
impl<'a> Intro<'a> {
pub fn new(title: &'a str, content: &'a str, fw_ok: bool) -> Self {
pub fn new(title: TString<'a>, content: TString<'a>, fw_ok: bool) -> Self {
Self {
bg: Pad::with_background(BLD_BG).with_clear(),
title: Child::new(Label::left_aligned(title, text_title(BLD_BG)).vertically_centered()),
@ -39,10 +42,10 @@ impl<'a> Intro<'a> {
.styled(button_bld_menu())
.with_expanded_touch_area(Insets::uniform(13)),
),
host: Child::new(Button::with_text("INSTALL FIRMWARE").styled(button_bld())),
host: Child::new(Button::with_text("INSTALL FIRMWARE".into()).styled(button_bld())),
text: Child::new(Label::left_aligned(content, TEXT_NORMAL).vertically_centered()),
warn: (!fw_ok).then_some(Child::new(
Label::new("FIRMWARE CORRUPTED", Alignment::Start, TEXT_WARNING)
Label::new("FIRMWARE CORRUPTED".into(), Alignment::Start, TEXT_WARNING)
.vertically_centered(),
)),
}

View File

@ -29,10 +29,10 @@ pub enum MenuMsg {
pub struct Menu {
bg: Pad,
title: Child<Label<&'static str>>,
close: Child<Button<&'static str>>,
reboot: Child<Button<&'static str>>,
reset: Child<Button<&'static str>>,
title: Child<Label<'static>>,
close: Child<Button>,
reboot: Child<Button>,
reset: Child<Button>,
}
impl Menu {
@ -43,7 +43,7 @@ impl Menu {
let mut instance = Self {
bg: Pad::with_background(BLD_BG),
title: Child::new(
Label::left_aligned("BOOTLOADER", text_title(BLD_BG)).vertically_centered(),
Label::left_aligned("BOOTLOADER".into(), text_title(BLD_BG)).vertically_centered(),
),
close: Child::new(
Button::with_icon(Icon::new(X32))

View File

@ -74,8 +74,8 @@ impl ModelTTFeatures {
let mut frame = ResultScreen::new(
&RESULT_FW_INSTALL,
Icon::new(CHECK40),
"Firmware installed\nsuccessfully",
Label::centered(msg, RESULT_FW_INSTALL.title_style()).vertically_centered(),
"Firmware installed\nsuccessfully".into(),
Label::centered(msg.into(), RESULT_FW_INSTALL.title_style()).vertically_centered(),
complete_draw,
);
show(&mut frame, complete_draw);
@ -85,8 +85,8 @@ impl ModelTTFeatures {
let mut frame = ResultScreen::new(
&RESULT_INITIAL,
Icon::new(CHECK40),
"Firmware installed\nsuccessfully",
Label::centered(msg, RESULT_INITIAL.title_style()).vertically_centered(),
"Firmware installed\nsuccessfully".into(),
Label::centered(msg.into(), RESULT_INITIAL.title_style()).vertically_centered(),
complete_draw,
);
show(&mut frame, complete_draw);
@ -133,8 +133,8 @@ impl UIFeaturesBootloader for ModelTTFeatures {
let mut frame = ResultScreen::new(
&RESULT_FW_INSTALL,
Icon::new(WARNING40),
"Firmware installation was not successful",
Label::centered(RECONNECT_MESSAGE, RESULT_FW_INSTALL.title_style())
"Firmware installation was not successful".into(),
Label::centered(RECONNECT_MESSAGE.into(), RESULT_FW_INSTALL.title_style())
.vertically_centered(),
true,
);
@ -164,14 +164,16 @@ impl UIFeaturesBootloader for ModelTTFeatures {
} else {
"DOWNGRADE FW"
};
let title = Label::left_aligned(title_str, TEXT_BOLD).vertically_centered();
let msg = Label::left_aligned(version_str.as_ref(), TEXT_NORMAL);
let alert =
(!should_keep_seed).then_some(Label::left_aligned("SEED WILL BE ERASED!", TEXT_BOLD));
let title = Label::left_aligned(title_str.into(), TEXT_BOLD).vertically_centered();
let msg = Label::left_aligned(version_str.as_str().into(), TEXT_NORMAL);
let alert = (!should_keep_seed).then_some(Label::left_aligned(
"SEED WILL BE ERASED!".into(),
TEXT_BOLD,
));
let (left, right) = if should_keep_seed {
let l = Button::with_text("CANCEL").styled(button_bld());
let r = Button::with_text("INSTALL").styled(button_confirm());
let l = Button::with_text("CANCEL".into()).styled(button_bld());
let r = Button::with_text("INSTALL".into()).styled(button_confirm());
(l, r)
} else {
let l = Button::with_icon(Icon::new(X24)).styled(button_bld());
@ -180,7 +182,11 @@ impl UIFeaturesBootloader for ModelTTFeatures {
};
let mut frame = Confirm::new(BLD_BG, left, right, ConfirmTitle::Text(title), msg)
.with_info("FW FINGERPRINT", fingerprint, button_bld_menu());
.with_info(
"FW FINGERPRINT".into(),
fingerprint.into(),
button_bld_menu(),
);
if let Some(alert) = alert {
frame = frame.with_alert(alert);
@ -193,13 +199,13 @@ impl UIFeaturesBootloader for ModelTTFeatures {
let icon = Icon::new(FIRE40);
let msg = Label::centered(
"Are you sure you want to factory reset the device?",
"Are you sure you want to factory reset the device?".into(),
TEXT_WIPE_NORMAL,
);
let alert = Label::centered("SEED AND FIRMWARE\nWILL BE ERASED!", TEXT_WIPE_BOLD);
let alert = Label::centered("SEED AND FIRMWARE\nWILL BE ERASED!".into(), TEXT_WIPE_BOLD);
let right = Button::with_text("RESET").styled(button_wipe_confirm());
let left = Button::with_text("CANCEL").styled(button_wipe_cancel());
let right = Button::with_text("RESET".into()).styled(button_wipe_confirm());
let left = Button::with_text("CANCEL".into()).styled(button_wipe_cancel());
let mut frame = Confirm::new(BLD_WIPE_COLOR, left, right, ConfirmTitle::Icon(icon), msg)
.with_alert(alert);
@ -230,7 +236,11 @@ impl UIFeaturesBootloader for ModelTTFeatures {
unwrap!(version_str.push_str("\nby "));
unwrap!(version_str.push_str(vendor));
let mut frame = Intro::new(title_str.as_str(), version_str.as_str(), fw_ok);
let mut frame = Intro::new(
title_str.as_str().into(),
version_str.as_str().into(),
fw_ok,
);
run(&mut frame)
}
@ -288,8 +298,9 @@ impl UIFeaturesBootloader for ModelTTFeatures {
let mut frame = ResultScreen::new(
&RESULT_WIPE,
Icon::new(CHECK40),
"Trezor reset\nsuccessfully",
Label::centered(RECONNECT_MESSAGE, RESULT_WIPE.title_style()).vertically_centered(),
"Trezor reset\nsuccessfully".into(),
Label::centered(RECONNECT_MESSAGE.into(), RESULT_WIPE.title_style())
.vertically_centered(),
true,
);
show(&mut frame, true);
@ -299,8 +310,9 @@ impl UIFeaturesBootloader for ModelTTFeatures {
let mut frame = ResultScreen::new(
&RESULT_WIPE,
Icon::new(WARNING40),
"Trezor reset was\nnot successful",
Label::centered(RECONNECT_MESSAGE, RESULT_WIPE.title_style()).vertically_centered(),
"Trezor reset was\nnot successful".into(),
Label::centered(RECONNECT_MESSAGE.into(), RESULT_WIPE.title_style())
.vertically_centered(),
true,
);
show(&mut frame, true);

View File

@ -19,9 +19,9 @@ use super::{theme, Frame, FrameMsg};
const MAX_XPUBS: usize = 16;
pub struct AddressDetails<T> {
qr_code: Frame<Qr, T>,
details: Frame<Paragraphs<ParagraphVecShort<'static>>, T>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>, T>,
qr_code: Frame<Qr>,
details: Frame<Paragraphs<ParagraphVecShort<'static>>>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
xpubs: Vec<(T, T), MAX_XPUBS>,
xpub_page_count: Vec<u8, MAX_XPUBS>,
current_page: usize,
@ -60,14 +60,14 @@ where
let result = Self {
qr_code: Frame::left_aligned(
theme::label_title(),
qr_title,
qr_title.into(),
Qr::new(qr_address, case_sensitive)?.with_border(7),
)
.with_cancel_button()
.with_border(theme::borders_horizontal_scroll()),
details: Frame::left_aligned(
theme::label_title(),
details_title,
details_title.into(),
para.into_paragraphs(),
)
.with_cancel_button()
@ -101,7 +101,7 @@ where
// repaint after page change so we can use a dummy context here.
let mut dummy_ctx = EventCtx::new();
self.xpub_view
.update_title(&mut dummy_ctx, self.xpubs[i].0.clone());
.update_title(&mut dummy_ctx, self.xpubs[i].0.clone().into());
self.xpub_view.update_content(&mut dummy_ctx, |p| {
p.inner_mut().update(self.xpubs[i].1.clone());
let npages = p.page_count();

View File

@ -1,18 +1,22 @@
use crate::ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
constant,
constant::screen,
display::{Color, Icon},
geometry::{Alignment2D, Insets, Offset, Point, Rect},
model_tt::{
component::{Button, ButtonMsg::Clicked, ButtonStyleSheet},
constant::WIDTH,
theme::{
bootloader::{
text_fingerprint, text_title, BUTTON_AREA_START, BUTTON_HEIGHT, CONTENT_PADDING,
CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, INFO32, TITLE_AREA, X32,
use crate::{
strutil::TString,
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
constant,
constant::screen,
display::{Color, Icon},
geometry::{Alignment2D, Insets, Offset, Point, Rect},
model_tt::{
component::{Button, ButtonMsg::Clicked, ButtonStyleSheet},
constant::WIDTH,
theme::{
bootloader::{
text_fingerprint, text_title, BUTTON_AREA_START, BUTTON_HEIGHT,
CONTENT_PADDING, CORNER_BUTTON_AREA, CORNER_BUTTON_TOUCH_EXPANSION, INFO32,
TITLE_AREA, X32,
},
WHITE,
},
WHITE,
},
},
};
@ -31,41 +35,38 @@ pub enum ConfirmMsg {
Confirm = 2,
}
pub enum ConfirmTitle<T> {
Text(Label<T>),
pub enum ConfirmTitle {
Text(Label<'static>),
Icon(Icon),
}
pub struct ConfirmInfo<T> {
pub title: Child<Label<T>>,
pub text: Child<Label<T>>,
pub info_button: Child<Button<&'static str>>,
pub close_button: Child<Button<&'static str>>,
pub struct ConfirmInfo<'a> {
pub title: Child<Label<'a>>,
pub text: Child<Label<'a>>,
pub info_button: Child<Button>,
pub close_button: Child<Button>,
}
pub struct Confirm<T> {
pub struct Confirm<'a> {
bg: Pad,
content_pad: Pad,
bg_color: Color,
title: ConfirmTitle<T>,
message: Child<Label<T>>,
alert: Option<Child<Label<T>>>,
left_button: Child<Button<T>>,
right_button: Child<Button<T>>,
info: Option<ConfirmInfo<T>>,
title: ConfirmTitle,
message: Child<Label<'a>>,
alert: Option<Child<Label<'static>>>,
left_button: Child<Button>,
right_button: Child<Button>,
info: Option<ConfirmInfo<'a>>,
show_info: bool,
}
impl<T> Confirm<T>
where
T: AsRef<str>,
{
impl<'a> Confirm<'a> {
pub fn new(
bg_color: Color,
left_button: Button<T>,
right_button: Button<T>,
title: ConfirmTitle<T>,
message: Label<T>,
left_button: Button,
right_button: Button,
title: ConfirmTitle,
message: Label<'a>,
) -> Self {
Self {
bg: Pad::with_background(bg_color).with_clear(),
@ -81,12 +82,17 @@ where
}
}
pub fn with_alert(mut self, alert: Label<T>) -> Self {
pub fn with_alert(mut self, alert: Label<'static>) -> Self {
self.alert = Some(Child::new(alert.vertically_centered()));
self
}
pub fn with_info(mut self, title: T, text: T, menu_button: ButtonStyleSheet) -> Self {
pub fn with_info(
mut self,
title: TString<'a>,
text: TString<'a>,
menu_button: ButtonStyleSheet,
) -> Self {
self.info = Some(ConfirmInfo {
title: Child::new(
Label::left_aligned(title, text_title(self.bg_color)).vertically_centered(),
@ -109,10 +115,7 @@ where
}
}
impl<T> Component for Confirm<T>
where
T: AsRef<str>,
{
impl Component for Confirm<'_> {
type Msg = ConfirmMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -242,10 +245,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Confirm<T>
where
T: AsRef<str>,
{
impl crate::trace::Trace for Confirm<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("BlConfirm");
}

View File

@ -1,6 +1,7 @@
#[cfg(feature = "haptic")]
use crate::trezorhal::haptic::{play, HapticEffect};
use crate::{
strutil::TString,
time::Duration,
ui::{
component::{
@ -21,22 +22,22 @@ pub enum ButtonMsg {
LongPressed,
}
pub struct Button<T> {
pub struct Button {
area: Rect,
touch_expand: Option<Insets>,
content: ButtonContent<T>,
content: ButtonContent,
styles: ButtonStyleSheet,
state: State,
long_press: Option<Duration>,
long_timer: Option<TimerToken>,
}
impl<T> Button<T> {
impl Button {
/// Offsets the baseline of the button text either up (negative) or down
/// (positive).
pub const BASELINE_OFFSET: i16 = -2;
pub const fn new(content: ButtonContent<T>) -> Self {
pub const fn new(content: ButtonContent) -> Self {
Self {
content,
area: Rect::zero(),
@ -48,7 +49,7 @@ impl<T> Button<T> {
}
}
pub const fn with_text(text: T) -> Self {
pub const fn with_text(text: TString<'static>) -> Self {
Self::new(ButtonContent::Text(text))
}
@ -117,17 +118,14 @@ impl<T> Button<T> {
matches!(self.state, State::Disabled)
}
pub fn set_content(&mut self, ctx: &mut EventCtx, content: ButtonContent<T>)
where
T: PartialEq,
{
pub fn set_content(&mut self, ctx: &mut EventCtx, content: ButtonContent) {
if self.content != content {
self.content = content;
ctx.request_paint();
}
}
pub fn content(&self) -> &ButtonContent<T> {
pub fn content(&self) -> &ButtonContent {
&self.content
}
@ -189,26 +187,24 @@ impl<T> Button<T> {
}
}
pub fn paint_content(&self, style: &ButtonStyle)
where
T: AsRef<str>,
{
pub fn paint_content(&self, style: &ButtonStyle) {
match &self.content {
ButtonContent::Empty => {}
ButtonContent::Text(text) => {
let text = text.as_ref();
let width = style.font.text_width(text);
let width = text.map(|c| style.font.text_width(c));
let height = style.font.text_height();
let start_of_baseline = self.area.center()
+ Offset::new(-width / 2, height / 2)
+ Offset::y(Self::BASELINE_OFFSET);
display::text_left(
start_of_baseline,
text,
style.font,
style.text_color,
style.button_color,
);
text.map(|text| {
display::text_left(
start_of_baseline,
text,
style.font,
style.text_color,
style.button_color,
);
});
}
ButtonContent::Icon(icon) => {
icon.draw(
@ -231,10 +227,7 @@ impl<T> Button<T> {
}
}
impl<T> Component for Button<T>
where
T: AsRef<str>,
{
impl Component for Button {
type Msg = ButtonMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -327,15 +320,12 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Button<T>
where
T: AsRef<str>,
{
impl crate::trace::Trace for Button {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Button");
match &self.content {
ButtonContent::Empty => {}
ButtonContent::Text(text) => t.string("text", text.as_ref().into()),
ButtonContent::Text(text) => t.string("text", *text),
ButtonContent::Icon(_) => t.bool("icon", true),
ButtonContent::IconAndText(content) => {
t.string("text", content.text.into());
@ -355,9 +345,9 @@ enum State {
}
#[derive(PartialEq, Eq)]
pub enum ButtonContent<T> {
pub enum ButtonContent {
Empty,
Text(T),
Text(TString<'static>),
Icon(Icon),
IconAndText(IconText),
IconBlend(Icon, Icon, Offset),
@ -381,19 +371,15 @@ pub struct ButtonStyle {
pub border_width: i16,
}
impl<T> Button<T> {
impl Button {
pub fn cancel_confirm(
left: Button<T>,
right: Button<T>,
left: Button,
right: Button,
left_is_small: bool,
) -> CancelConfirm<
T,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
>
where
T: AsRef<str>,
{
> {
let width = if left_is_small {
theme::BUTTON_WIDTH
} else {
@ -412,21 +398,17 @@ impl<T> Button<T> {
}
pub fn cancel_confirm_text(
left: Option<T>,
right: Option<T>,
left: Option<TString<'static>>,
right: Option<TString<'static>>,
) -> CancelConfirm<
T,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
>
where
T: AsRef<str>,
{
> {
let left_is_small: bool;
let left = if let Some(verb) = left {
left_is_small = verb.as_ref().len() <= 4;
if verb.as_ref() == "^" {
left_is_small = verb.len() <= 4;
if verb == "^".into() {
Button::with_icon(theme::ICON_UP)
} else {
Button::with_text(verb)
@ -444,17 +426,13 @@ impl<T> Button<T> {
}
pub fn cancel_info_confirm(
confirm: T,
info: T,
confirm: TString<'static>,
info: TString<'static>,
) -> CancelInfoConfirm<
T,
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
>
where
T: AsRef<str>,
{
> {
let right = Button::with_text(confirm)
.styled(theme::button_confirm())
.map(|msg| {
@ -479,16 +457,12 @@ impl<T> Button<T> {
}
pub fn select_word(
words: [T; 3],
words: [TString<'static>; 3],
) -> CancelInfoConfirm<
T,
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
impl Fn(ButtonMsg) -> Option<SelectWordMsg>,
>
where
T: AsRef<str>,
{
> {
let btn = move |i, word| {
Button::with_text(word)
.styled(theme::button_pin())
@ -521,11 +495,10 @@ pub enum CancelConfirmMsg {
Confirmed,
}
type CancelInfoConfirm<T, F0, F1, F2> = FixedHeightBar<
Split<MsgMap<Button<T>, F0>, Split<MsgMap<Button<T>, F1>, MsgMap<Button<T>, F2>>>,
>;
type CancelInfoConfirm<F0, F1, F2> =
FixedHeightBar<Split<MsgMap<Button, F0>, Split<MsgMap<Button, F1>, MsgMap<Button, F2>>>>;
type CancelConfirm<T, F0, F1> = FixedHeightBar<Split<MsgMap<Button<T>, F0>, MsgMap<Button<T>, F1>>>;
type CancelConfirm<F0, F1> = FixedHeightBar<Split<MsgMap<Button, F0>, MsgMap<Button, F1>>>;
#[derive(Clone, Copy)]
pub enum CancelInfoConfirmMsg {

View File

@ -3,7 +3,7 @@ use core::mem;
use crate::{
error::Error,
maybe_trace::MaybeTrace,
micropython::buffer::StrBuffer,
strutil::TString,
translations::TR,
ui::{
component::{
@ -25,50 +25,44 @@ const LOADER_INNER: i16 = 28;
const LOADER_OFFSET: i16 = -34;
const LOADER_SPEED: u16 = 5;
pub struct CoinJoinProgress<T, U> {
pub struct CoinJoinProgress<U> {
value: u16,
indeterminate: bool,
content: Child<Frame<Split<Empty, U>, StrBuffer>>,
content: Child<Frame<Split<Empty, U>>>,
// Label is not a child since circular loader paints large black rectangle which overlaps it.
// To work around this, draw label every time loader is drawn.
label: Label<T>,
label: Label<'static>,
}
impl<T, U> CoinJoinProgress<T, U>
where
T: AsRef<str>,
{
impl<U> CoinJoinProgress<U> {
pub fn new(
text: T,
text: TString<'static>,
indeterminate: bool,
) -> Result<CoinJoinProgress<T, impl Component<Msg = Never> + MaybeTrace>, Error>
where
T: AsRef<str>,
{
) -> Result<CoinJoinProgress<impl Component<Msg = Never> + MaybeTrace>, Error> {
let style = theme::label_coinjoin_progress();
let label = Label::centered(
TryInto::<StrBuffer>::try_into(TR::coinjoin__title_do_not_disconnect)?,
style,
)
.vertically_centered();
let label = Label::centered(TR::coinjoin__title_do_not_disconnect.into(), style)
.vertically_centered();
let bg = painter::rect_painter(style.background_color, theme::BG);
let inner = (bg, label);
CoinJoinProgress::with_background(text, inner, indeterminate)
}
}
impl<T, U> CoinJoinProgress<T, U>
impl<U> CoinJoinProgress<U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
pub fn with_background(text: T, inner: U, indeterminate: bool) -> Result<Self, Error> {
pub fn with_background(
text: TString<'static>,
inner: U,
indeterminate: bool,
) -> Result<Self, Error> {
Ok(Self {
value: 0,
indeterminate,
content: Frame::centered(
theme::label_title(),
TR::coinjoin__title_progress.try_into()?,
TR::coinjoin__title_progress.into(),
Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner),
)
.into_child(),
@ -77,9 +71,8 @@ where
}
}
impl<T, U> Component for CoinJoinProgress<T, U>
impl<U> Component for CoinJoinProgress<U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
type Msg = Never;
@ -132,9 +125,8 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for CoinJoinProgress<T, U>
impl<U> crate::trace::Trace for CoinJoinProgress<U>
where
T: AsRef<str>,
U: Component + crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -1,7 +1,10 @@
use crate::ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
geometry::{Alignment2D, Point, Rect},
use crate::{
strutil::TString,
ui::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
geometry::{Alignment2D, Point, Rect},
},
};
use crate::ui::model_tt::{
@ -19,15 +22,15 @@ const STYLE: &ResultStyle = &crate::ui::model_tt::theme::bootloader::RESULT_WIPE
#[cfg(not(feature = "bootloader"))]
const STYLE: &ResultStyle = &super::theme::RESULT_ERROR;
pub struct ErrorScreen<'a, T> {
pub struct ErrorScreen<'a> {
bg: Pad,
title: Child<Label<T>>,
message: Child<Label<T>>,
footer: Child<ResultFooter<'a, T>>,
title: Child<Label<'a>>,
message: Child<Label<'a>>,
footer: Child<ResultFooter<'a>>,
}
impl<T: AsRef<str>> ErrorScreen<'_, T> {
pub fn new(title: T, message: T, footer: T) -> Self {
impl<'a> ErrorScreen<'a> {
pub fn new(title: TString<'a>, message: TString<'a>, footer: TString<'a>) -> Self {
let title = Label::centered(title, STYLE.title_style());
let message = Label::centered(message, STYLE.message_style());
let footer = ResultFooter::new(
@ -44,7 +47,7 @@ impl<T: AsRef<str>> ErrorScreen<'_, T> {
}
}
impl<T: AsRef<str>> Component for ErrorScreen<'_, T> {
impl<'a> Component for ErrorScreen<'a> {
type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect {
@ -62,7 +65,7 @@ impl<T: AsRef<str>> Component for ErrorScreen<'_, T> {
);
self.message.place(message_area);
let (_, bottom_area) = ResultFooter::<T>::split_bounds();
let (_, bottom_area) = ResultFooter::split_bounds();
self.footer.place(bottom_area);
screen()

View File

@ -1,11 +1,14 @@
use crate::ui::{
component::{image::Image, Child, Component, Event, EventCtx, Label},
display,
geometry::{Insets, Rect},
model_tt::component::{
fido_icons::get_fido_icon_data,
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
use crate::{
strutil::TString,
ui::{
component::{image::Image, Child, Component, Event, EventCtx, Label},
display,
geometry::{Insets, Rect},
model_tt::component::{
fido_icons::get_fido_icon_data,
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
},
};
@ -23,10 +26,10 @@ pub enum FidoMsg {
Cancelled,
}
pub struct FidoConfirm<F: Fn(usize) -> T, T, U> {
pub struct FidoConfirm<F: Fn(usize) -> TString<'static>, U> {
page_swipe: Swipe,
app_name: Label<T>,
account_name: Label<T>,
app_name: Label<'static>,
account_name: Label<'static>,
icon: Child<Image>,
/// Function/closure that will return appropriate page on demand.
get_account: F,
@ -35,20 +38,19 @@ pub struct FidoConfirm<F: Fn(usize) -> T, T, U> {
controls: U,
}
impl<F, T, U> FidoConfirm<F, T, U>
impl<F, U> FidoConfirm<F, U>
where
F: Fn(usize) -> T,
T: AsRef<str> + From<&'static str>,
F: Fn(usize) -> TString<'static>,
U: Component<Msg = CancelConfirmMsg>,
{
pub fn new(
app_name: T,
app_name: TString<'static>,
get_account: F,
page_count: usize,
icon_name: Option<T>,
icon_name: Option<TString<'static>>,
controls: U,
) -> Self {
let icon_data = get_fido_icon_data(icon_name.as_ref());
let icon_data = get_fido_icon_data(icon_name);
// Preparing scrollbar and setting its page-count.
let mut scrollbar = ScrollBar::horizontal();
@ -116,10 +118,9 @@ where
}
}
impl<F, T, U> Component for FidoConfirm<F, T, U>
impl<F, U> Component for FidoConfirm<F, U>
where
F: Fn(usize) -> T,
T: AsRef<str> + From<&'static str>,
F: Fn(usize) -> TString<'static>,
U: Component<Msg = CancelConfirmMsg>,
{
type Msg = FidoMsg;
@ -198,8 +199,8 @@ where
// Account name is optional.
// Showing it only if it differs from app name.
// (Dummy requests usually have some text as both app_name and account_name.)
let account_name = self.account_name.text().as_ref();
let app_name = self.app_name.text().as_ref();
let account_name = self.account_name.text();
let app_name = self.app_name.text();
if !account_name.is_empty() && account_name != app_name {
self.account_name.paint();
}
@ -219,9 +220,9 @@ where
}
#[cfg(feature = "ui_debug")]
impl<F, T, U> crate::trace::Trace for FidoConfirm<F, T, U>
impl<F, T> crate::trace::Trace for FidoConfirm<F, T>
where
F: Fn(usize) -> T,
F: Fn(usize) -> TString<'static>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("FidoConfirm");

View File

@ -3,6 +3,9 @@
//! do not edit manually!
use crate::strutil::TString;
const ICON_APPLE: &[u8] = include_res!("model_tt/res/fido/icon_apple.toif");
const ICON_AWS: &[u8] = include_res!("model_tt/res/fido/icon_aws.toif");
const ICON_BINANCE: &[u8] = include_res!("model_tt/res/fido/icon_binance.toif");
@ -39,9 +42,9 @@ const ICON_WEBAUTHN: &[u8] = include_res!("model_tt/res/fido/icon_webauthn.toif"
/// Translates icon name into its data.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// supplied.
pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8] {
pub fn get_fido_icon_data(icon_name: Option<TString<'static>>) -> &'static [u8] {
if let Some(icon_name) = icon_name {
match icon_name.as_ref() {
icon_name.map(|c| match c {
"apple" => ICON_APPLE,
"aws" => ICON_AWS,
"binance" => ICON_BINANCE,
@ -73,7 +76,7 @@ pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8]
"stripe" => ICON_STRIPE,
"tutanota" => ICON_TUTANOTA,
_ => ICON_WEBAUTHN,
}
})
} else {
ICON_WEBAUTHN
}

View File

@ -2,6 +2,9 @@
//! (by running `make templates` in `core`)
//! do not edit manually!
use crate::strutil::TString;
<%
icons: list[tuple[str, str]] = []
for app in fido:
@ -21,14 +24,14 @@ const ICON_WEBAUTHN: &[u8] = include_res!("model_tt/res/fido/icon_webauthn.toif"
/// Translates icon name into its data.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// supplied.
pub fn get_fido_icon_data<T: AsRef<str>>(icon_name: Option<T>) -> &'static [u8] {
pub fn get_fido_icon_data(icon_name: Option<TString<'static>>) -> &'static [u8] {
if let Some(icon_name) = icon_name {
match icon_name.as_ref() {
icon_name.map(|c| match c {
% for icon_name, var_name in icons:
"${icon_name}" => ICON_${var_name},
% endfor
_ => ICON_WEBAUTHN,
}
})
} else {
ICON_WEBAUTHN
}

View File

@ -1,18 +1,21 @@
use super::theme;
use crate::ui::{
component::{
base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx,
use crate::{
strutil::TString,
ui::{
component::{
base::ComponentExt, label::Label, text::TextStyle, Child, Component, Event, EventCtx,
},
display::Icon,
geometry::{Alignment, Insets, Offset, Rect},
model_tt::component::{Button, ButtonMsg, CancelInfoConfirmMsg},
},
display::Icon,
geometry::{Alignment, Insets, Offset, Rect},
model_tt::component::{Button, ButtonMsg, CancelInfoConfirmMsg},
};
pub struct Frame<T, U> {
pub struct Frame<T> {
border: Insets,
title: Child<Label<U>>,
subtitle: Option<Child<Label<U>>>,
button: Option<Child<Button<&'static str>>>,
title: Child<Label<'static>>,
subtitle: Option<Child<Label<'static>>>,
button: Option<Child<Button>>,
button_msg: CancelInfoConfirmMsg,
content: Child<T>,
}
@ -22,12 +25,16 @@ pub enum FrameMsg<T> {
Button(CancelInfoConfirmMsg),
}
impl<T, U> Frame<T, U>
impl<T> Frame<T>
where
T: Component,
U: AsRef<str>,
{
pub fn new(style: TextStyle, alignment: Alignment, title: U, content: T) -> Self {
pub fn new(
style: TextStyle,
alignment: Alignment,
title: TString<'static>,
content: T,
) -> Self {
Self {
title: Child::new(Label::new(title, alignment, style)),
subtitle: None,
@ -38,15 +45,15 @@ where
}
}
pub fn left_aligned(style: TextStyle, title: U, content: T) -> Self {
pub fn left_aligned(style: TextStyle, title: TString<'static>, content: T) -> Self {
Self::new(style, Alignment::Start, title, content)
}
pub fn right_aligned(style: TextStyle, title: U, content: T) -> Self {
pub fn right_aligned(style: TextStyle, title: TString<'static>, content: T) -> Self {
Self::new(style, Alignment::End, title, content)
}
pub fn centered(style: TextStyle, title: U, content: T) -> Self {
pub fn centered(style: TextStyle, title: TString<'static>, content: T) -> Self {
Self::new(style, Alignment::Center, title, content)
}
@ -55,7 +62,7 @@ where
self
}
pub fn with_subtitle(mut self, style: TextStyle, subtitle: U) -> Self {
pub fn with_subtitle(mut self, style: TextStyle, subtitle: TString<'static>) -> Self {
self.subtitle = Some(Child::new(Label::new(
subtitle,
self.title.inner().alignment(),
@ -91,7 +98,7 @@ where
self.content.inner()
}
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: U) {
pub fn update_title(&mut self, ctx: &mut EventCtx, new_title: TString<'static>) {
self.title.mutate(ctx, |ctx, t| {
t.set_text(new_title);
t.request_complete_repaint(ctx)
@ -110,10 +117,9 @@ where
}
}
impl<T, U> Component for Frame<T, U>
impl<T> Component for Frame<T>
where
T: Component,
U: AsRef<str>,
{
type Msg = FrameMsg<T::Msg>;
@ -179,10 +185,9 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Frame<T, U>
impl<T> crate::trace::Trace for Frame<T>
where
T: crate::trace::Trace,
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Frame");

View File

@ -269,14 +269,14 @@ impl crate::trace::Trace for Homescreen {
}
}
pub struct Lockscreen {
label: TString<'static>,
pub struct Lockscreen<'a> {
label: TString<'a>,
bootscreen: bool,
coinjoin_authorized: bool,
}
impl Lockscreen {
pub fn new(label: TString<'static>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
impl<'a> Lockscreen<'a> {
pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
Lockscreen {
label,
bootscreen,
@ -285,7 +285,7 @@ impl Lockscreen {
}
}
impl Component for Lockscreen {
impl Component for Lockscreen<'_> {
type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -401,7 +401,7 @@ fn is_image_toif(buffer: &[u8]) -> bool {
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Lockscreen {
impl crate::trace::Trace for Lockscreen<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Lockscreen");
}

View File

@ -21,9 +21,9 @@ use heapless::String;
const MAX_LENGTH: usize = 8;
pub struct Bip39Input {
button: Button<&'static str>,
button: Button,
// used only to keep track of suggestion text color
button_suggestion: Button<&'static str>,
button_suggestion: Button,
textbox: TextBox<MAX_LENGTH>,
multi_tap: MultiTapKeyboard,
options_num: Option<usize>,
@ -263,7 +263,7 @@ impl Bip39Input {
// Disabled button.
self.button.disable(ctx);
self.button.set_stylesheet(ctx, theme::button_pin());
self.button.set_content(ctx, ButtonContent::Text(""));
self.button.set_content(ctx, ButtonContent::Text("".into()));
}
}
}

View File

@ -1,9 +1,12 @@
use crate::ui::{
component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe},
geometry::{Alignment2D, Grid, Offset, Rect},
model_tt::{
component::{Button, ButtonMsg, Swipe, SwipeDirection},
theme,
use crate::{
strutil::TString,
ui::{
component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe},
geometry::{Alignment2D, Grid, Offset, Rect},
model_tt::{
component::{Button, ButtonMsg, Swipe, SwipeDirection},
theme,
},
},
};
@ -14,27 +17,26 @@ pub enum MnemonicKeyboardMsg {
Previous,
}
pub struct MnemonicKeyboard<T, U> {
pub struct MnemonicKeyboard<T> {
/// Initial prompt, displayed on empty input.
prompt: Child<Maybe<Label<U>>>,
prompt: Child<Maybe<Label<'static>>>,
/// Backspace button.
back: Child<Maybe<Button<&'static str>>>,
back: Child<Maybe<Button>>,
/// Input area, acting as the auto-complete and confirm button.
input: Child<Maybe<T>>,
/// Key buttons.
keys: [Child<Button<&'static str>>; MNEMONIC_KEY_COUNT],
keys: [Child<Button>; MNEMONIC_KEY_COUNT],
/// Swipe controller - allowing for going to the previous word.
swipe: Swipe,
/// Whether going back is allowed (is not on the very first word).
can_go_back: bool,
}
impl<T, U> MnemonicKeyboard<T, U>
impl<T> MnemonicKeyboard<T>
where
T: MnemonicInput,
U: AsRef<str>,
{
pub fn new(input: T, prompt: U, can_go_back: bool) -> Self {
pub fn new(input: T, prompt: TString<'static>, can_go_back: bool) -> Self {
// Input might be already pre-filled
let prompt_visible = input.is_empty();
@ -57,7 +59,7 @@ where
)),
input: Child::new(Maybe::new(theme::BG, input, !prompt_visible)),
keys: T::keys()
.map(|t| Button::with_text(t).styled(theme::button_pin()))
.map(|t| Button::with_text(t.into()).styled(theme::button_pin()))
.map(Child::new),
swipe: Swipe::new().right(),
can_go_back,
@ -99,10 +101,9 @@ where
}
}
impl<T, U> Component for MnemonicKeyboard<T, U>
impl<T> Component for MnemonicKeyboard<T>
where
T: MnemonicInput,
U: AsRef<str>,
{
type Msg = MnemonicKeyboardMsg;
@ -210,10 +211,9 @@ pub enum MnemonicInputMsg {
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for MnemonicKeyboard<T, U>
impl<T> crate::trace::Trace for MnemonicKeyboard<T>
where
T: MnemonicInput + crate::trace::Trace,
U: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("MnemonicKeyboard");

View File

@ -1,16 +1,19 @@
use crate::ui::{
component::{
base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Never,
use crate::{
strutil::TString,
ui::{
component::{
base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Never,
},
display,
geometry::{Grid, Offset, Rect},
model_tt::component::{
button::{Button, ButtonContent, ButtonMsg},
keyboard::common::{paint_pending_marker, MultiTapKeyboard},
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
util::long_line_content_with_ellipsis,
},
display,
geometry::{Grid, Offset, Rect},
model_tt::component::{
button::{Button, ButtonContent, ButtonMsg},
keyboard::common::{paint_pending_marker, MultiTapKeyboard},
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
util::long_line_content_with_ellipsis,
};
use core::cell::Cell;
@ -23,9 +26,9 @@ pub enum PassphraseKeyboardMsg {
pub struct PassphraseKeyboard {
page_swipe: Swipe,
input: Child<Input>,
back: Child<Button<&'static str>>,
confirm: Child<Button<&'static str>>,
keys: [Child<Button<&'static str>>; KEY_COUNT],
back: Child<Button>,
confirm: Child<Button>,
keys: [Child<Button>; KEY_COUNT],
scrollbar: ScrollBar,
fade: Cell<bool>,
}
@ -69,20 +72,20 @@ impl PassphraseKeyboard {
}
}
fn key_text(content: &ButtonContent<&'static str>) -> &'static str {
fn key_text(content: &ButtonContent) -> TString<'static> {
match content {
ButtonContent::Text(text) => text,
ButtonContent::Icon(_) => " ",
ButtonContent::IconAndText(_) => " ",
ButtonContent::Empty => "",
ButtonContent::IconBlend(_, _, _) => "",
ButtonContent::Text(text) => *text,
ButtonContent::Icon(_) => " ".into(),
ButtonContent::IconAndText(_) => " ".into(),
ButtonContent::Empty => "".into(),
ButtonContent::IconBlend(_, _, _) => "".into(),
}
}
fn key_content(text: &'static str) -> ButtonContent<&'static str> {
fn key_content(text: &'static str) -> ButtonContent {
match text {
" " => ButtonContent::Icon(theme::ICON_SPACE),
t => ButtonContent::Text(t),
t => ButtonContent::Text(t.into()),
}
}
@ -272,7 +275,7 @@ impl Component for PassphraseKeyboard {
// character in textbox. If not, let's just append the first character.
let text = Self::key_text(btn.inner().content());
self.input.mutate(ctx, |ctx, i| {
let edit = i.multi_tap.click_key(ctx, key, text);
let edit = text.map(|c| i.multi_tap.click_key(ctx, key, c));
i.textbox.apply(ctx, edit);
});
self.after_edit(ctx);

View File

@ -2,6 +2,7 @@ use core::mem;
use heapless::String;
use crate::{
strutil::TString,
time::Duration,
trezorhal::random,
ui::{
@ -39,32 +40,29 @@ const HEADER_PADDING: Insets = Insets::new(
HEADER_PADDING_SIDE,
);
pub struct PinKeyboard<T> {
pub struct PinKeyboard<'a> {
allow_cancel: bool,
major_prompt: Child<Label<T>>,
minor_prompt: Child<Label<T>>,
major_warning: Option<Child<Label<T>>>,
major_prompt: Child<Label<'a>>,
minor_prompt: Child<Label<'a>>,
major_warning: Option<Child<Label<'a>>>,
textbox: Child<PinDots>,
textbox_pad: Pad,
erase_btn: Child<Maybe<Button<&'static str>>>,
cancel_btn: Child<Maybe<Button<&'static str>>>,
confirm_btn: Child<Button<&'static str>>,
digit_btns: [Child<Button<&'static str>>; DIGIT_COUNT],
erase_btn: Child<Maybe<Button>>,
cancel_btn: Child<Maybe<Button>>,
confirm_btn: Child<Button>,
digit_btns: [Child<Button>; DIGIT_COUNT],
warning_timer: Option<TimerToken>,
}
impl<T> PinKeyboard<T>
where
T: AsRef<str>,
{
impl<'a> PinKeyboard<'a> {
// Label position fine-tuning.
const MAJOR_OFF: Offset = Offset::y(11);
const MINOR_OFF: Offset = Offset::y(11);
pub fn new(
major_prompt: T,
minor_prompt: T,
major_warning: Option<T>,
major_prompt: TString<'a>,
minor_prompt: TString<'a>,
major_warning: Option<TString<'a>>,
allow_cancel: bool,
) -> Self {
// Control buttons.
@ -102,12 +100,12 @@ where
}
}
fn generate_digit_buttons() -> [Child<Button<&'static str>>; DIGIT_COUNT] {
fn generate_digit_buttons() -> [Child<Button>; DIGIT_COUNT] {
// Generate a random sequence of digits from 0 to 9.
let mut digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
random::shuffle(&mut digits);
digits
.map(Button::with_text)
.map(|c| Button::with_text(c.into()))
.map(|b| b.styled(theme::button_pin()))
.map(Child::new)
}
@ -146,10 +144,7 @@ where
}
}
impl<T> Component for PinKeyboard<T>
where
T: AsRef<str>,
{
impl Component for PinKeyboard<'_> {
type Msg = PinKeyboardMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -238,7 +233,9 @@ where
for btn in &mut self.digit_btns {
if let Some(Clicked) = btn.event(ctx, event) {
if let ButtonContent::Text(text) = btn.inner().content() {
self.textbox.mutate(ctx, |ctx, t| t.push(ctx, text));
text.map(|text| {
self.textbox.mutate(ctx, |ctx, t| t.push(ctx, text));
});
self.pin_modified(ctx);
return None;
}
@ -460,18 +457,18 @@ impl Component for PinDots {
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for PinKeyboard<T>
where
T: AsRef<str>,
{
impl crate::trace::Trace for PinKeyboard<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("PinKeyboard");
// So that debuglink knows the locations of the buttons
let mut digits_order: String<10> = String::new();
for btn in self.digit_btns.iter() {
let btn_content = btn.inner().content();
if let ButtonContent::Text(text) = btn_content {
unwrap!(digits_order.push_str(text));
text.map(|text| {
unwrap!(digits_order.push_str(text));
});
}
}
t.string("digits_order", digits_order.as_str().into());

View File

@ -28,7 +28,7 @@ use crate::{
const MAX_LENGTH: usize = 8;
pub struct Slip39Input {
button: Button<&'static str>,
button: Button,
textbox: TextBox<MAX_LENGTH>,
multi_tap: MultiTapKeyboard,
final_word: Option<&'static str>,
@ -293,7 +293,7 @@ impl Slip39Input {
} else {
// Disabled button.
self.button.disable(ctx);
self.button.set_content(ctx, ButtonContent::Text(""));
self.button.set_content(ctx, ButtonContent::Text("".into()));
}
}

View File

@ -12,7 +12,7 @@ const LABELS: [&str; 5] = ["12", "18", "20", "24", "33"];
const CELLS: [(usize, usize); 5] = [(0, 0), (0, 2), (0, 4), (1, 0), (1, 2)];
pub struct SelectWordCount {
button: [Button<&'static str>; NUMBERS.len()],
button: [Button; NUMBERS.len()],
}
pub enum SelectWordCountMsg {
@ -22,7 +22,7 @@ pub enum SelectWordCountMsg {
impl SelectWordCount {
pub fn new() -> Self {
SelectWordCount {
button: LABELS.map(|t| Button::with_text(t).styled(theme::button_pin())),
button: LABELS.map(|t| Button::with_text(t.into()).styled(theme::button_pin())),
}
}
}

View File

@ -1,6 +1,5 @@
use crate::{
error::Error,
micropython::buffer::StrBuffer,
strutil::{self, StringType},
translations::TR,
ui::{
@ -31,8 +30,8 @@ where
input: Child<NumberInput>,
paragraphs: Child<Paragraphs<Paragraph<'static>>>,
paragraphs_pad: Pad,
info_button: Child<Button<StrBuffer>>,
confirm_button: Child<Button<StrBuffer>>,
info_button: Child<Button>,
confirm_button: Child<Button>,
}
impl<T, F> NumberInputDialog<T, F>
@ -48,8 +47,8 @@ where
input: NumberInput::new(min, max, init_value).into_child(),
paragraphs: Paragraphs::new(Paragraph::new(&theme::TEXT_NORMAL, text)).into_child(),
paragraphs_pad: Pad::with_background(theme::BG),
info_button: Button::with_text(TR::buttons__info.try_into()?).into_child(),
confirm_button: Button::with_text(TR::buttons__continue.try_into()?)
info_button: Button::with_text(TR::buttons__info.into()).into_child(),
confirm_button: Button::with_text(TR::buttons__continue.into())
.styled(theme::button_confirm())
.into_child(),
})
@ -154,8 +153,8 @@ pub enum NumberInputMsg {
pub struct NumberInput {
area: Rect,
dec: Child<Button<&'static str>>,
inc: Child<Button<&'static str>>,
dec: Child<Button>,
inc: Child<Button>,
min: u32,
max: u32,
value: u32,
@ -163,10 +162,10 @@ pub struct NumberInput {
impl NumberInput {
pub fn new(min: u32, max: u32, value: u32) -> Self {
let dec = Button::with_text("-")
let dec = Button::with_text("-".into())
.styled(theme::button_counter())
.into_child();
let inc = Button::with_text("+")
let inc = Button::with_text("+".into())
.styled(theme::button_counter())
.into_child();
let value = value.clamp(min, max);
@ -217,7 +216,7 @@ impl Component for NumberInput {
let mut buf = [0u8; 10];
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
let digit_font = Font::DEMIBOLD;
let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET;
let y_offset = digit_font.text_height() / 2 + Button::BASELINE_OFFSET;
display::rect_fill(self.area, theme::BG);
display::text_center(
self.area.center() + Offset::y(y_offset),

View File

@ -1,6 +1,6 @@
use crate::{
error::Error,
micropython::buffer::StrBuffer,
strutil::TString,
time::Instant,
translations::TR,
ui::{
@ -19,7 +19,7 @@ use super::{
/// Allows pagination of inner component. Shows scroll bar, confirm & cancel
/// buttons. Optionally handles hold-to-confirm with loader.
pub struct ButtonPage<T, U> {
pub struct ButtonPage<T> {
/// Inner component.
content: T,
/// Cleared when page changes.
@ -29,10 +29,10 @@ pub struct ButtonPage<T, U> {
scrollbar: ScrollBar,
/// Hold-to-confirm mode whenever this is `Some(loader)`.
loader: Option<Loader>,
button_cancel: Option<Button<U>>,
button_confirm: Button<U>,
button_prev: Button<&'static str>,
button_next: Button<&'static str>,
button_cancel: Option<Button>,
button_confirm: Button,
button_prev: Button,
button_next: Button,
/// Show cancel button instead of back button.
cancel_from_any_page: bool,
/// Whether to pass-through left swipe to parent component.
@ -43,24 +43,23 @@ pub struct ButtonPage<T, U> {
fade: Option<u16>,
}
impl<T> ButtonPage<T, StrBuffer>
impl<T> ButtonPage<T>
where
T: Paginate,
T: Component,
{
pub fn with_hold(mut self) -> Result<Self, Error> {
self.button_confirm = Button::with_text(TR::buttons__hold_to_confirm.try_into()?)
.styled(theme::button_confirm());
self.button_confirm =
Button::with_text(TR::buttons__hold_to_confirm.into()).styled(theme::button_confirm());
self.loader = Some(Loader::new());
Ok(self)
}
}
impl<T, U> ButtonPage<T, U>
impl<T> ButtonPage<T>
where
T: Paginate,
T: Component,
U: AsRef<str> + From<&'static str>,
{
pub fn new(content: T, background: Color) -> Self {
Self {
@ -85,13 +84,17 @@ where
self
}
pub fn with_cancel_confirm(mut self, left: Option<U>, right: Option<U>) -> Self {
pub fn with_cancel_confirm(
mut self,
left: Option<TString<'static>>,
right: Option<TString<'static>>,
) -> Self {
let cancel = match left {
Some(verb) => match verb.as_ref() {
Some(verb) => verb.map(|s| match s {
"^" => Button::with_icon(theme::ICON_UP),
"<" => Button::with_icon(theme::ICON_BACK),
_ => Button::with_text(verb),
},
}),
_ => Button::with_icon(theme::ICON_CANCEL),
};
let confirm = match right {
@ -282,11 +285,10 @@ enum HandleResult<T> {
Continue,
}
impl<T, U> Component for ButtonPage<T, U>
impl<T> Component for ButtonPage<T>
where
T: Paginate,
T: Component,
U: AsRef<str> + From<&'static str>,
{
type Msg = PageMsg<T::Msg>;
@ -294,7 +296,7 @@ where
let small_left_button = match (&self.button_cancel, &self.button_confirm) {
(None, _) => true,
(Some(cancel), confirm) => match (cancel.content(), confirm.content()) {
(ButtonContent::Text(t), _) => t.as_ref().len() <= 4,
(ButtonContent::Text(t), _) => t.len() <= 4,
(ButtonContent::Icon(_), ButtonContent::Icon(_)) => false,
_ => true,
},
@ -425,7 +427,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for ButtonPage<T, U>
impl<T> crate::trace::Trace for ButtonPage<T>
where
T: crate::trace::Trace,
{
@ -528,10 +530,7 @@ mod tests {
#[test]
fn paragraphs_empty() {
let mut page = ButtonPage::<_, &'static str>::new(
Paragraphs::<[Paragraph<'static>; 0]>::new([]),
theme::BG,
);
let mut page = ButtonPage::new(Paragraphs::<[Paragraph<'static>; 0]>::new([]), theme::BG);
page.place(SCREEN);
let expected = serde_json::json!({
@ -554,7 +553,7 @@ mod tests {
#[test]
fn paragraphs_single() {
let mut page = ButtonPage::<_, &'static str>::new(
let mut page = ButtonPage::new(
Paragraphs::new([
Paragraph::new(
&theme::TEXT_NORMAL,
@ -592,7 +591,7 @@ mod tests {
#[test]
fn paragraphs_one_long() {
let mut page = ButtonPage::<_, &'static str>::new(
let mut page = ButtonPage::new(
Paragraphs::new(
Paragraph::new(
&theme::TEXT_BOLD,
@ -650,7 +649,7 @@ mod tests {
#[test]
fn paragraphs_three_long() {
let mut page = ButtonPage::<_, &'static str>::new(
let mut page = ButtonPage::new(
Paragraphs::new([
Paragraph::new(
&theme::TEXT_BOLD,
@ -750,7 +749,7 @@ mod tests {
#[test]
fn paragraphs_hard_break() {
let mut page = ButtonPage::<_, &'static str>::new(
let mut page = ButtonPage::new(
Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, "Short one.").break_after(),
Paragraph::new(&theme::TEXT_NORMAL, "Short two.").break_after(),

View File

@ -1,7 +1,7 @@
use core::mem;
use crate::{
strutil::{StringType, TString},
strutil::TString,
ui::{
component::{
base::ComponentExt,
@ -18,8 +18,8 @@ use crate::{
use super::theme;
pub struct Progress<T> {
title: Child<Label<T>>,
pub struct Progress {
title: Child<Label<'static>>,
value: u16,
loader_y_offset: i16,
indeterminate: bool,
@ -27,13 +27,14 @@ pub struct Progress<T> {
description_pad: Pad,
}
impl<T> Progress<T>
where
T: StringType,
{
impl Progress {
const AREA: Rect = constant::screen().inset(theme::borders());
pub fn new(title: T, indeterminate: bool, description: TString<'static>) -> Self {
pub fn new(
title: TString<'static>,
indeterminate: bool,
description: TString<'static>,
) -> Self {
Self {
title: Label::centered(title, theme::label_progress()).into_child(),
value: 0,
@ -48,10 +49,7 @@ where
}
}
impl<T> Component for Progress<T>
where
T: StringType,
{
impl Component for Progress {
type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect {
@ -117,10 +115,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Progress<T>
where
T: StringType,
{
impl crate::trace::Trace for Progress {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Progress");
}

View File

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{text::TextStyle, Child, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
@ -41,21 +41,23 @@ impl ResultStyle {
}
}
pub struct ResultFooter<'a, T> {
pub struct ResultFooter<'a> {
style: &'a ResultStyle,
text: Label<T>,
text: Label<'a>,
area: Rect,
}
impl<'a, T: AsRef<str>> ResultFooter<'a, T> {
pub fn new(text: Label<T>, style: &'a ResultStyle) -> Self {
impl<'a> ResultFooter<'a> {
pub fn new(text: Label<'a>, style: &'a ResultStyle) -> Self {
Self {
style,
text,
area: Rect::zero(),
}
}
}
impl ResultFooter<'_> {
pub const fn split_bounds() -> (Rect, Rect) {
let main_area = Rect::new(
Point::new(RESULT_PADDING, 0),
@ -69,7 +71,7 @@ impl<'a, T: AsRef<str>> ResultFooter<'a, T> {
}
}
impl<T: AsRef<str>> Component for ResultFooter<'_, T> {
impl Component for ResultFooter<'_> {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
@ -78,6 +80,10 @@ impl<T: AsRef<str>> Component for ResultFooter<'_, T> {
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
// divider line
let bar = Rect::from_center_and_size(
@ -89,27 +95,23 @@ impl<T: AsRef<str>> Component for ResultFooter<'_, T> {
// footer text
self.text.paint();
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
}
pub struct ResultScreen<'a, T> {
pub struct ResultScreen<'a> {
bg: Pad,
footer_pad: Pad,
style: &'a ResultStyle,
icon: Icon,
message: Child<Label<T>>,
footer: Child<ResultFooter<'a, &'a str>>,
message: Child<Label<'a>>,
footer: Child<ResultFooter<'a>>,
}
impl<'a, T: StringType> ResultScreen<'a, T> {
impl<'a> ResultScreen<'a> {
pub fn new(
style: &'a ResultStyle,
icon: Icon,
message: T,
footer: Label<&'a str>,
message: TString<'a>,
footer: Label<'a>,
complete_draw: bool,
) -> Self {
let mut instance = Self {
@ -130,13 +132,13 @@ impl<'a, T: StringType> ResultScreen<'a, T> {
}
}
impl<'a, T: StringType> Component for ResultScreen<'a, T> {
impl<'a> Component for ResultScreen<'a> {
type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect {
self.bg.place(screen());
let (main_area, footer_area) = ResultFooter::<&'a str>::split_bounds();
let (main_area, footer_area) = ResultFooter::split_bounds();
self.footer_pad.place(footer_area);
self.footer.place(footer_area);

View File

@ -100,10 +100,9 @@ impl TryFrom<SelectWordCountMsg> for Obj {
}
}
impl<F, T, U> ComponentMsgObj for FidoConfirm<F, T, U>
impl<F, U> ComponentMsgObj for FidoConfirm<F, U>
where
F: Fn(usize) -> T,
T: StringType,
F: Fn(usize) -> TString<'static>,
U: Component<Msg = CancelConfirmMsg>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
@ -141,10 +140,7 @@ where
}
}
impl<T> ComponentMsgObj for PinKeyboard<T>
where
T: AsRef<str>,
{
impl ComponentMsgObj for PinKeyboard<'_> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
PinKeyboardMsg::Confirmed => self.pin().try_into(),
@ -162,10 +158,9 @@ impl ComponentMsgObj for PassphraseKeyboard {
}
}
impl<T, U> ComponentMsgObj for MnemonicKeyboard<T, U>
impl<T> ComponentMsgObj for MnemonicKeyboard<T>
where
T: MnemonicInput,
U: AsRef<str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
@ -181,10 +176,9 @@ where
}
}
impl<T, U> ComponentMsgObj for Frame<T, U>
impl<T> ComponentMsgObj for Frame<T>
where
T: ComponentMsgObj,
U: AsRef<str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
@ -194,10 +188,9 @@ where
}
}
impl<T, U> ComponentMsgObj for ButtonPage<T, U>
impl<T> ComponentMsgObj for ButtonPage<T>
where
T: Component + Paginate,
U: AsRef<str> + From<&'static str>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
@ -269,10 +262,7 @@ where
}
}
impl<T> ComponentMsgObj for Progress<T>
where
T: StringType,
{
impl ComponentMsgObj for Progress {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!()
}
@ -286,7 +276,7 @@ impl ComponentMsgObj for Homescreen {
}
}
impl ComponentMsgObj for Lockscreen {
impl ComponentMsgObj for Lockscreen<'_> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
HomescreenMsg::Dismissed => Ok(CANCELLED.as_obj()),
@ -342,9 +332,8 @@ where
}
}
impl<T, U> ComponentMsgObj for CoinJoinProgress<T, U>
impl<U> ComponentMsgObj for CoinJoinProgress<U>
where
T: AsRef<str>,
U: Component<Msg = Never>,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
@ -352,10 +341,7 @@ where
}
}
impl<T> ComponentMsgObj for super::component::bl_confirm::Confirm<T>
where
T: AsRef<str>,
{
impl ComponentMsgObj for super::component::bl_confirm::Confirm<'_> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
super::component::bl_confirm::ConfirmMsg::Cancel => Ok(CANCELLED.as_obj()),
@ -401,12 +387,17 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
let mut page = if hold {
ButtonPage::new(paragraphs, theme::BG).with_hold()?
} else {
ButtonPage::new(paragraphs, theme::BG).with_cancel_confirm(verb_cancel, verb)
ButtonPage::new(paragraphs, theme::BG)
.with_cancel_confirm(verb_cancel.map(|c| c.into()), verb.map(|c| c.into()))
};
if hold && hold_danger {
page = page.with_confirm_style(theme::button_danger())
}
let obj = LayoutObj::new(Frame::left_aligned(theme::label_title(), title, page))?;
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title.into(),
page,
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -438,9 +429,9 @@ extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *m
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
ButtonPage::new(FormattedText::new(ops).vertically_centered(), theme::BG)
.with_cancel_confirm(None, verb),
.with_cancel_confirm(None, verb.map(|v| v.into())),
))?;
Ok(obj.into())
};
@ -530,14 +521,14 @@ impl ConfirmBlobParams {
let mut page = ButtonPage::new(paragraphs, theme::BG);
if let Some(verb) = self.verb {
page = page.with_cancel_confirm(self.verb_cancel, Some(verb))
page = page.with_cancel_confirm(self.verb_cancel.map(|v| v.into()), Some(verb.into()))
}
if self.hold {
page = page.with_hold()?
}
let mut frame = Frame::left_aligned(theme::label_title(), self.title, page);
let mut frame = Frame::left_aligned(theme::label_title(), self.title.into(), page);
if let Some(subtitle) = self.subtitle {
frame = frame.with_subtitle(theme::label_subtitle(), subtitle);
frame = frame.with_subtitle(theme::label_subtitle(), subtitle.into());
}
if self.info_button {
frame = frame.with_info_button();
@ -604,10 +595,10 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut
let obj = LayoutObj::new(
Frame::left_aligned(
theme::label_title(),
title,
title.into(),
ButtonPage::new(paragraphs, theme::BG)
.with_swipe_left()
.with_cancel_confirm(None, Some(verb)),
.with_cancel_confirm(None, Some(verb.into())),
)
.with_info_button(),
)?;
@ -628,13 +619,17 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
&theme::TEXT_MONO,
&theme::TEXT_MONO,
)?;
let page: ButtonPage<_, StrBuffer> = if hold {
let page = if hold {
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold()?
} else {
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.with_cancel_confirm(None, Some(TR::buttons__confirm.try_into()?))
.with_cancel_confirm(None, Some(TR::buttons__confirm.into()))
};
let obj = LayoutObj::new(Frame::left_aligned(theme::label_title(), title, page))?;
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title.into(),
page,
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -665,11 +660,10 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
_ => return Err(value_error!("Invalid image.")),
};
let tr_change: StrBuffer = TR::buttons__change.try_into()?;
let buttons = Button::cancel_confirm_text(None, Some(tr_change));
let buttons = Button::cancel_confirm_text(None, Some(TR::buttons__change.into()));
let obj = LayoutObj::new(Frame::centered(
theme::label_title(),
title,
title.into(),
Dialog::new(painter::jpeg_painter(buffer_func, size, 1), buttons),
))?;
Ok(obj.into())
@ -691,12 +685,12 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs:
let paragraphs = Paragraphs::new(par_array);
let buttons = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL),
Button::with_text(button).styled(theme::button_confirm()),
Button::with_text(button.into()).styled(theme::button_confirm()),
true,
);
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
Dialog::new(paragraphs, buttons),
))?;
Ok(obj.into())
@ -768,7 +762,7 @@ extern "C" fn new_show_info_with_cancel(n_args: usize, args: *const Obj, kwargs:
let obj = LayoutObj::new(
Frame::left_aligned(
theme::label_title(),
title,
title.into(),
SimplePage::new(paragraphs.into_paragraphs(), axis, theme::BG)
.with_swipe_right_to_go_back(),
)
@ -824,15 +818,14 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
paragraphs.add(Paragraph::new(&theme::TEXT_NORMAL, label).no_break());
paragraphs.add(Paragraph::new(&theme::TEXT_MONO, value));
}
let mut page: ButtonPage<_, StrBuffer> =
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold()?;
let mut page = ButtonPage::new(paragraphs.into_paragraphs(), theme::BG).with_hold()?;
if cancel_arrow {
page = page.with_cancel_arrow()
}
if info_button {
page = page.with_swipe_left();
}
let mut frame = Frame::left_aligned(theme::label_title(), title, page);
let mut frame = Frame::left_aligned(theme::label_title(), title.into(), page);
if info_button {
frame = frame.with_info_button();
}
@ -861,12 +854,11 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs:
Paragraph::new(&theme::TEXT_MONO, amount_new),
]);
let tr_title: StrBuffer = TR::modify_amount__title.try_into()?;
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
tr_title,
ButtonPage::<_, StrBuffer>::new(paragraphs, theme::BG)
.with_cancel_confirm(Some("^".into()), Some(TR::buttons__continue.try_into()?)),
TR::modify_amount__title.into(),
ButtonPage::new(paragraphs, theme::BG)
.with_cancel_confirm(Some("^".into()), Some(TR::buttons__continue.into())),
))?;
Ok(obj.into())
};
@ -908,8 +900,8 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m
let obj = LayoutObj::new(
Frame::left_aligned(
theme::label_title(),
title,
ButtonPage::<_, StrBuffer>::new(paragraphs, theme::BG)
title.into(),
ButtonPage::new(paragraphs, theme::BG)
.with_hold()?
.with_swipe_left(),
)
@ -963,7 +955,7 @@ fn new_show_modal(
title,
Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL),
Button::with_text(button).styled(button_style),
Button::with_text(button.into()).styled(button_style),
false,
),
)
@ -977,9 +969,9 @@ fn new_show_modal(
IconDialog::new(
icon,
title,
theme::button_bar(Button::with_text(button).styled(button_style).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
theme::button_bar(Button::with_text(button.into()).styled(button_style).map(
|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed),
)),
)
.with_value(value)
.with_description(description),
@ -1008,7 +1000,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
let block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let app_name: StrBuffer = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?;
let icon: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?;
let icon: Option<TString> = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?;
let accounts: Gc<List> = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?;
// Cache the page count so that we can move `accounts` into the closure.
@ -1023,14 +1015,17 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
let controls = Button::cancel_confirm(
Button::with_icon(theme::ICON_CANCEL),
Button::<StrBuffer>::with_text(TR::buttons__confirm.try_into()?)
.styled(theme::button_confirm()),
Button::with_text(TR::buttons__confirm.into()).styled(theme::button_confirm()),
true,
);
let fido_page = FidoConfirm::new(app_name, get_page, page_count, icon, controls);
let fido_page = FidoConfirm::new(app_name.into(), get_page, page_count, icon, controls);
let obj = LayoutObj::new(Frame::centered(theme::label_title(), title, fido_page))?;
let obj = LayoutObj::new(Frame::centered(
theme::label_title(),
title.into(),
fido_page,
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1098,7 +1093,7 @@ extern "C" fn new_show_mismatch(n_args: usize, args: *const Obj, kwargs: *mut Ma
title,
Button::cancel_confirm(
Button::with_icon(theme::ICON_BACK),
Button::with_text(button).styled(theme::button_reset()),
Button::with_text(button.into()).styled(theme::button_reset()),
true,
),
)
@ -1128,10 +1123,10 @@ extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map)
let obj = if let Some(t) = title {
LayoutObj::new(Frame::left_aligned(
theme::label_title(),
t,
t.into(),
Dialog::new(
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]),
theme::button_bar(Button::with_text(button).map(|msg| {
theme::button_bar(Button::with_text(button.into()).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
),
@ -1142,7 +1137,7 @@ extern "C" fn new_show_simple(n_args: usize, args: *const Obj, kwargs: *mut Map)
theme::borders(),
Dialog::new(
Paragraphs::new([Paragraph::new(&theme::TEXT_NORMAL, description)]),
theme::button_bar(Button::with_text(button).map(|msg| {
theme::button_bar(Button::with_text(button.into()).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
),
@ -1185,11 +1180,11 @@ extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mu
}
}
let buttons = Button::cancel_info_confirm(button, info_button);
let buttons = Button::cancel_info_confirm(button.into(), info_button.into());
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
Dialog::new(paragraphs.into_paragraphs(), buttons),
))?;
Ok(obj.into())
@ -1214,9 +1209,9 @@ extern "C" fn new_confirm_more(n_args: usize, args: *const Obj, kwargs: *mut Map
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.with_cancel_confirm(None, Some(button))
.with_cancel_confirm(None, Some(button.into()))
.with_confirm_style(theme::button_default())
.with_back_button(),
))?;
@ -1237,11 +1232,10 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut
Paragraph::new(&theme::TEXT_MONO, max_feerate),
]);
let tr_title: StrBuffer = TR::coinjoin__title.try_into()?;
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
tr_title,
ButtonPage::<_, StrBuffer>::new(paragraphs, theme::BG).with_hold()?,
TR::coinjoin__title.into(),
ButtonPage::new(paragraphs, theme::BG).with_hold()?,
))?;
Ok(obj.into())
};
@ -1255,11 +1249,16 @@ extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map)
let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?;
let warning: bool = kwargs.get_or(Qstr::MP_QSTR_wrong_pin, false)?;
let warning = if warning {
Some(TR::pin__wrong_pin.try_into()?)
Some(TR::pin__wrong_pin.into())
} else {
None
};
let obj = LayoutObj::new(PinKeyboard::new(prompt, subprompt, warning, allow_cancel))?;
let obj = LayoutObj::new(PinKeyboard::new(
prompt.into(),
subprompt.into(),
warning,
allow_cancel,
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1282,7 +1281,7 @@ extern "C" fn new_request_bip39(n_args: usize, args: *const Obj, kwargs: *mut Ma
let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?;
let obj = LayoutObj::new(MnemonicKeyboard::new(
Bip39Input::prefilled_word(prefill_word.as_ref()),
prompt,
prompt.into(),
can_go_back,
))?;
Ok(obj.into())
@ -1297,7 +1296,7 @@ extern "C" fn new_request_slip39(n_args: usize, args: *const Obj, kwargs: *mut M
let can_go_back: bool = kwargs.get(Qstr::MP_QSTR_can_go_back)?.try_into()?;
let obj = LayoutObj::new(MnemonicKeyboard::new(
Slip39Input::prefilled_word(prefill_word.as_ref()),
prompt,
prompt.into(),
can_go_back,
))?;
Ok(obj.into())
@ -1310,12 +1309,12 @@ extern "C" fn new_select_word(n_args: usize, args: *const Obj, kwargs: *mut 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: [StrBuffer; 3] = util::iter_into_array(words_iterable)?;
let words: [TString<'static>; 3] = util::iter_into_array(words_iterable)?;
let paragraphs = Paragraphs::new([Paragraph::new(&theme::TEXT_DEMIBOLD, description)]);
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
Dialog::new(paragraphs, Button::select_word(words)),
))?;
Ok(obj.into())
@ -1336,8 +1335,8 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
ButtonPage::<_, StrBuffer>::new(paragraphs.into_paragraphs(), theme::BG)
title.into(),
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.with_hold()?
.without_cancel(),
))?;
@ -1366,7 +1365,7 @@ extern "C" fn new_request_number(n_args: usize, args: *const Obj, kwargs: *mut M
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
NumberInputDialog::new(min_count, max_count, count, callback)?,
))?;
Ok(obj.into())
@ -1394,7 +1393,7 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
Dialog::new(
Checklist::from_paragraphs(
theme::ICON_LIST_CURRENT,
@ -1407,7 +1406,7 @@ extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut M
.with_check_width(theme::CHECKLIST_CHECK_WIDTH)
.with_current_offset(theme::CHECKLIST_CURRENT_OFFSET)
.with_done_offset(theme::CHECKLIST_DONE_OFFSET),
theme::button_bar(Button::with_text(button).map(|msg| {
theme::button_bar(Button::with_text(button.into()).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
),
@ -1440,20 +1439,23 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut
let obj = if info_button {
LayoutObj::new(Frame::left_aligned(
theme::label_title(),
notification,
notification.into(),
Dialog::new(
paragraphs,
Button::<StrBuffer>::cancel_info_confirm(
TR::buttons__continue.try_into()?,
TR::buttons__more_info.try_into()?,
Button::cancel_info_confirm(
TR::buttons__continue.into(),
TR::buttons__more_info.into(),
),
),
))?
} else {
LayoutObj::new(Frame::left_aligned(
theme::label_title(),
notification,
Dialog::new(paragraphs, Button::cancel_confirm_text(None, Some(button))),
notification.into(),
Dialog::new(
paragraphs,
Button::cancel_confirm_text(None, Some(button.into())),
),
))?
};
Ok(obj.into())
@ -1477,7 +1479,7 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
title,
title.into(),
Dialog::new(paragraphs, SelectWordCount::new()),
))?;
Ok(obj.into())
@ -1496,11 +1498,9 @@ extern "C" fn new_show_group_share_success(
let obj = LayoutObj::new(IconDialog::new_shares(
lines,
theme::button_bar(
Button::<StrBuffer>::with_text(TR::buttons__continue.try_into()?).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
}),
),
theme::button_bar(Button::with_text(TR::buttons__continue.into()).map(|msg| {
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
})),
))?;
Ok(obj.into())
};
@ -1519,12 +1519,11 @@ extern "C" fn new_show_remaining_shares(n_args: usize, args: *const Obj, kwargs:
.add(Paragraph::new(&theme::TEXT_NORMAL, description).break_after());
}
let tr_title: StrBuffer = TR::recovery__title_remaining_shares.try_into()?;
let obj = LayoutObj::new(Frame::left_aligned(
theme::label_title(),
tr_title,
ButtonPage::<_, StrBuffer>::new(paragraphs.into_paragraphs(), theme::BG)
.with_cancel_confirm(None, Some(TR::buttons__continue.try_into()?))
TR::recovery__title_remaining_shares.into(),
ButtonPage::new(paragraphs.into_paragraphs(), theme::BG)
.with_cancel_confirm(None, Some(TR::buttons__continue.into()))
.with_confirm_style(theme::button_default())
.without_cancel(),
))?;
@ -1550,7 +1549,11 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
// Description updates are received as &str and we need to provide a way to
// convert them to StrBuffer.
let obj = LayoutObj::new(Progress::new(title, indeterminate, description.into()))?;
let obj = LayoutObj::new(Progress::new(
title.into(),
indeterminate,
description.into(),
))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
@ -1565,7 +1568,7 @@ extern "C" fn new_show_progress_coinjoin(n_args: usize, args: *const Obj, kwargs
// The second type parameter is actually not used in `new()` but we need to
// provide it.
let progress = CoinJoinProgress::<_, Never>::new(title, indeterminate)?;
let progress = CoinJoinProgress::<Never>::new(title.into(), indeterminate)?;
let obj = if time_ms > 0 && indeterminate {
let timeout = Timeout::new(time_ms);
LayoutObj::new((timeout, progress.map(|_msg| None)))?
@ -1642,19 +1645,17 @@ extern "C" fn new_confirm_firmware_update(
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
let fingerprint: StrBuffer = kwargs.get(Qstr::MP_QSTR_fingerprint)?.try_into()?;
let title_str = TR::firmware_update__title.try_into()?;
let title_str = TR::firmware_update__title.into();
let title = Label::left_aligned(title_str, theme::TEXT_BOLD).vertically_centered();
let msg = Label::left_aligned(description, theme::TEXT_NORMAL);
let msg = Label::left_aligned(description.into(), theme::TEXT_NORMAL);
let left =
Button::with_text(TR::buttons__cancel.try_into()?).styled(theme::button_default());
let right =
Button::with_text(TR::buttons__install.try_into()?).styled(theme::button_confirm());
let left = Button::with_text(TR::buttons__cancel.into()).styled(theme::button_default());
let right = Button::with_text(TR::buttons__install.into()).styled(theme::button_confirm());
let obj = LayoutObj::new(
Confirm::new(theme::BG, left, right, ConfirmTitle::Text(title), msg).with_info(
TR::firmware_update__title_fingerprint.try_into()?,
fingerprint,
TR::firmware_update__title_fingerprint.into(),
fingerprint.into(),
theme::button_moreinfo(),
),
)?;
@ -2190,8 +2191,11 @@ mod tests {
#[test]
fn trace_example_layout() {
let buttons =
Button::cancel_confirm(Button::with_text("Left"), Button::with_text("Right"), false);
let buttons = Button::cancel_confirm(
Button::with_text("Left".into()),
Button::with_text("Right".into()),
false,
);
let ops = OpTextLayout::new(theme::TEXT_NORMAL)
.text_normal("Testing text layout, with some text, and some more text. And ")

View File

@ -1,5 +1,3 @@
#[cfg(feature = "micropython")]
use crate::micropython::buffer::StrBuffer;
use crate::ui::{
component::Component,
constant::screen,
@ -10,26 +8,8 @@ use crate::ui::{
},
};
#[cfg(not(feature = "micropython"))]
// SAFETY: Actually safe but see below
unsafe fn get_str(text: &str) -> &str {
text
}
#[cfg(feature = "micropython")]
// SAFETY: The caller is responsible for ensuring that the StrBuffer does not
// escape the lifetime of the original &str.
unsafe fn get_str(text: &str) -> StrBuffer {
unsafe { StrBuffer::from_ptr_and_len(text.as_ptr(), text.len()) }
}
pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
// SAFETY: these will get placed into `frame` which does not outlive this
// function
let title = unsafe { get_str(title) };
let msg = unsafe { get_str(msg) };
let footer = unsafe { get_str(footer) };
let mut frame = ErrorScreen::new(title, msg, footer);
let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into());
frame.place(constant::screen());
frame.paint();
}