diff --git a/core/embed/rust/src/micropython/buffer.rs b/core/embed/rust/src/micropython/buffer.rs index 86b1aa2313..ee92471114 100644 --- a/core/embed/rust/src/micropython/buffer.rs +++ b/core/embed/rust/src/micropython/buffer.rs @@ -1,10 +1,6 @@ use core::{convert::TryFrom, ops::Deref, ptr, slice, str}; -use crate::{ - error::Error, - micropython::obj::Obj, - strutil::{hexlify, SkipPrefix}, -}; +use crate::{error::Error, micropython::obj::Obj, strutil::hexlify}; use super::ffi; @@ -93,10 +89,8 @@ impl StrBuffer { unsafe { slice::from_raw_parts(self.ptr.add(self.off.into()), self.len.into()) } } } -} -impl SkipPrefix for StrBuffer { - fn skip_prefix(&self, skip_bytes: usize) -> Self { + pub fn skip_prefix(&self, skip_bytes: usize) -> Self { let off: u16 = unwrap!(skip_bytes.try_into()); assert!(off <= self.len); assert!(self.as_ref().is_char_boundary(skip_bytes)); diff --git a/core/embed/rust/src/strutil.rs b/core/embed/rust/src/strutil.rs index 592710ae94..3210ddc4a1 100644 --- a/core/embed/rust/src/strutil.rs +++ b/core/embed/rust/src/strutil.rs @@ -9,38 +9,18 @@ use crate::micropython::{buffer::StrBuffer, obj::Obj}; #[cfg(feature = "translations")] use crate::translations::TR; -/// Trait for slicing off string prefix by a specified number of bytes. -/// See `StringType` for deeper explanation. -pub trait SkipPrefix { - fn skip_prefix(&self, bytes: usize) -> Self; -} - -// XXX only implemented in bootloader, as we don't want &str to satisfy -// StringType in the main firmware. This is because we want to avoid duplication -// of every StringType-parametrized component. -#[cfg(feature = "bootloader")] -impl SkipPrefix for &str { - fn skip_prefix(&self, chars: usize) -> Self { - &self[chars..] - } -} - -/// Trait for internal representation of strings. -/// Exists so that we can support `StrBuffer` as well as `&str` in the UI -/// components. Implies the following operations: -/// - dereference into a short-lived `&str` reference (AsRef) -/// - create a new string by skipping some number of bytes (SkipPrefix) - used -/// when rendering continuations of long strings +/// Trait for internal representation of strings. This is a legacy crutch before +/// we fully transition to `TString`. For now, it allows some manner of +/// compatibility between `&str` and `StrBuffer`. Implies the following +/// operations: +/// - dereference into a short-lived `&str` reference (AsRef) (probably not +/// strictly necessary anymore) /// - create a new string from a string literal (From<&'static str>) -pub trait StringType: - AsRef + From<&'static str> + Into> + SkipPrefix -{ -} +/// - infallibly convert into a `TString` (Into>), which is +/// then used for other operations. +pub trait StringType: AsRef + From<&'static str> + Into> {} -impl StringType for T where - T: AsRef + From<&'static str> + Into> + SkipPrefix -{ -} +impl StringType for T where T: AsRef + From<&'static str> + Into> {} /// Unified-length String type, long enough for most simple use-cases. pub type ShortString = String<50>; @@ -129,6 +109,23 @@ impl TString<'_> { Self::Str(s) => fun(s), } } + + pub fn skip_prefix(&self, skip_bytes: usize) -> Self { + self.map(|s| { + assert!(skip_bytes <= s.len()); + assert!(s.is_char_boundary(skip_bytes)); + }); + match self { + #[cfg(feature = "micropython")] + Self::Allocated(s) => Self::Allocated(s.skip_prefix(skip_bytes)), + #[cfg(feature = "translations")] + Self::Translation { tr, offset } => Self::Translation { + tr: *tr, + offset: offset + skip_bytes as u16, + }, + Self::Str(s) => Self::Str(&s[skip_bytes..]), + } + } } impl TString<'static> { @@ -198,22 +195,3 @@ impl<'a, 'b> PartialEq> for TString<'b> { } impl Eq for TString<'_> {} - -impl SkipPrefix for TString<'_> { - fn skip_prefix(&self, skip_bytes: usize) -> Self { - self.map(|s| { - assert!(skip_bytes <= s.len()); - assert!(s.is_char_boundary(skip_bytes)); - }); - match self { - #[cfg(feature = "micropython")] - Self::Allocated(s) => Self::Allocated(s.skip_prefix(skip_bytes)), - #[cfg(feature = "translations")] - Self::Translation { tr, offset } => Self::Translation { - tr: *tr, - offset: offset + skip_bytes as u16, - }, - Self::Str(s) => Self::Str(&s[skip_bytes..]), - } - } -} diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index 29e88cc9c7..6703440b7d 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -3,6 +3,7 @@ use core::mem; use heapless::Vec; use crate::{ + strutil::TString, time::Duration, ui::{ component::{maybe::PaintOverlapping, MsgMap}, @@ -415,7 +416,7 @@ where } #[derive(Copy, Clone, PartialEq, Eq)] -pub enum Event<'a> { +pub enum Event { #[cfg(feature = "button")] Button(ButtonEvent), #[cfg(feature = "touch")] @@ -425,7 +426,7 @@ pub enum Event<'a> { /// token (another timer has to be requested). Timer(TimerToken), /// Advance progress bar. Progress screens only. - Progress(u16, &'a str), + Progress(u16, TString<'static>), /// Component has been attached to component tree. This event is sent once /// before any other events. Attach, diff --git a/core/embed/rust/src/ui/component/text/formatted.rs b/core/embed/rust/src/ui/component/text/formatted.rs index 58613f3ae6..48d97811e8 100644 --- a/core/embed/rust/src/ui/component/text/formatted.rs +++ b/core/embed/rust/src/ui/component/text/formatted.rs @@ -1,9 +1,6 @@ -use crate::{ - strutil::StringType, - ui::{ - component::{Component, Event, EventCtx, Never, Paginate}, - geometry::{Alignment, Offset, Rect}, - }, +use crate::ui::{ + component::{Component, Event, EventCtx, Never, Paginate}, + geometry::{Alignment, Offset, Rect}, }; use super::{ @@ -12,15 +9,15 @@ use super::{ }; #[derive(Clone)] -pub struct FormattedText { - op_layout: OpTextLayout, +pub struct FormattedText { + op_layout: OpTextLayout<'static>, vertical: Alignment, char_offset: usize, y_offset: i16, } -impl FormattedText { - pub fn new(op_layout: OpTextLayout) -> Self { +impl FormattedText { + pub fn new(op_layout: OpTextLayout<'static>) -> Self { Self { op_layout, vertical: Alignment::Start, @@ -54,7 +51,7 @@ impl FormattedText { } // Pagination -impl Paginate for FormattedText { +impl Paginate for FormattedText { fn page_count(&mut self) -> usize { let mut page_count = 1; // There's always at least one page. @@ -118,7 +115,7 @@ impl Paginate for FormattedText { } } -impl Component for FormattedText { +impl Component for FormattedText { type Msg = Never; fn place(&mut self, bounds: Rect) -> Rect { @@ -145,7 +142,7 @@ impl Component for FormattedText { // DEBUG-ONLY SECTION BELOW #[cfg(feature = "ui_debug")] -impl crate::trace::Trace for FormattedText { +impl crate::trace::Trace for FormattedText { fn trace(&self, t: &mut dyn crate::trace::Tracer) { use crate::ui::component::text::layout::trace::TraceSink; use core::cell::Cell; diff --git a/core/embed/rust/src/ui/component/text/op.rs b/core/embed/rust/src/ui/component/text/op.rs index ccaad640bf..6e4d11413f 100644 --- a/core/embed/rust/src/ui/component/text/op.rs +++ b/core/embed/rust/src/ui/component/text/op.rs @@ -1,5 +1,5 @@ use crate::{ - strutil::StringType, + strutil::TString, ui::{ display::{Color, Font}, geometry::{Alignment, Offset, Rect}, @@ -26,12 +26,12 @@ const PROCESSED_CHARS_ONE: usize = 1; #[derive(Clone)] /// Extension of TextLayout, allowing for Op-based operations -pub struct OpTextLayout { +pub struct OpTextLayout<'a> { pub layout: TextLayout, - ops: Vec, MAX_OPS>, + ops: Vec, MAX_OPS>, } -impl<'a, T: StringType + Clone + 'a> OpTextLayout { +impl<'a> OpTextLayout<'a> { pub fn new(style: TextStyle) -> Self { Self { layout: TextLayout::new(style), @@ -116,7 +116,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout { // (just for incomplete texts that were separated) layout.continues_from_prev_page = continued; - let fit = layout.layout_text(text.as_ref(), cursor, sink); + let fit = text.map(|t| layout.layout_text(t, cursor, sink)); match fit { LayoutFit::Fitting { @@ -148,9 +148,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout { /// Gets rid of all action-Ops that are before the `skip_bytes` threshold. /// (Not removing the style changes, e.g. Font or Color, because they need /// to be correctly set for future Text operations.) - fn filter_skipped_ops<'b, I>(ops_iter: I, skip_bytes: usize) -> impl Iterator> + 'b + fn filter_skipped_ops<'b, I>( + ops_iter: I, + skip_bytes: usize, + ) -> impl Iterator> + 'b where - I: Iterator> + 'b, + I: Iterator> + 'b, 'a: 'b, { let mut skipped = 0; @@ -158,7 +161,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout { match op { Op::Text(text, _continued) if skipped < skip_bytes => { let skip_text_bytes_if_fits_partially = skip_bytes - skipped; - skipped = skipped.saturating_add(text.as_ref().len()); + skipped = skipped.saturating_add(text.len()); if skipped > skip_bytes { // Fits partially // Skipping some bytes at the beginning, leaving rest @@ -187,15 +190,15 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout { } // Op-adding operations -impl OpTextLayout { - pub fn with_new_item(mut self, item: Op) -> Self { +impl<'a> OpTextLayout<'a> { + pub fn with_new_item(mut self, item: Op<'a>) -> Self { self.ops .push(item) .assert_if_debugging_ui("Could not push to self.ops - increase MAX_OPS."); self } - pub fn text(self, text: T) -> Self { + pub fn text(self, text: TString<'a>) -> Self { self.with_new_item(Op::Text(text, false)) } @@ -237,21 +240,21 @@ impl OpTextLayout { } // Op-adding aggregation operations -impl OpTextLayout { - pub fn text_normal(self, text: T) -> Self { - self.font(Font::NORMAL).text(text) +impl<'a> OpTextLayout<'a> { + pub fn text_normal(self, text: impl Into>) -> Self { + self.font(Font::NORMAL).text(text.into()) } - pub fn text_mono(self, text: T) -> Self { - self.font(Font::MONO).text(text) + pub fn text_mono(self, text: impl Into>) -> Self { + self.font(Font::MONO).text(text.into()) } - pub fn text_bold(self, text: T) -> Self { - self.font(Font::BOLD).text(text) + pub fn text_bold(self, text: impl Into>) -> Self { + self.font(Font::BOLD).text(text.into()) } - pub fn text_demibold(self, text: T) -> Self { - self.font(Font::DEMIBOLD).text(text) + pub fn text_demibold(self, text: impl Into>) -> Self { + self.font(Font::DEMIBOLD).text(text.into()) } pub fn chunkify_text(self, chunks: Option<(Chunks, i16)>) -> Self { @@ -264,11 +267,11 @@ impl OpTextLayout { } #[derive(Clone)] -pub enum Op { +pub enum Op<'a> { /// Render text with current color and font. /// Bool signifies whether this is a split Text Op continued from previous /// page. If true, a leading ellipsis will be rendered. - Text(T, bool), + Text(TString<'a>, bool), /// Set current text color. Color(Color), /// Set currently used font. diff --git a/core/embed/rust/src/ui/component/text/paragraphs.rs b/core/embed/rust/src/ui/component/text/paragraphs.rs index 919341e0ab..1fd658c134 100644 --- a/core/embed/rust/src/ui/component/text/paragraphs.rs +++ b/core/embed/rust/src/ui/component/text/paragraphs.rs @@ -1,7 +1,7 @@ use heapless::Vec; use crate::{ - strutil::StringType, + strutil::TString, ui::{ component::{Component, Event, EventCtx, Never, Paginate}, display::toif::Icon, @@ -26,16 +26,13 @@ pub const PARAGRAPH_TOP_SPACE: i16 = -1; /// Offset of paragraph bounding box bottom relative to bottom of its text. pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5; -pub type ParagraphVecLong = Vec, 32>; -pub type ParagraphVecShort = Vec, 8>; - -pub trait ParagraphSource { - /// Determines the output type produced. - type StrType: StringType; +pub type ParagraphVecLong<'a> = Vec, 32>; +pub type ParagraphVecShort<'a> = Vec, 8>; +pub trait ParagraphSource<'a> { /// Return text and associated style for given paragraph index and character /// offset within the paragraph. - fn at(&self, index: usize, offset: usize) -> Paragraph; + fn at(&self, index: usize, offset: usize) -> Paragraph<'a>; /// Number of paragraphs. fn size(&self) -> usize; @@ -56,9 +53,9 @@ pub struct Paragraphs { source: T, } -impl Paragraphs +impl<'a, T> Paragraphs where - T: ParagraphSource, + T: ParagraphSource<'a>, { pub fn new(source: T) -> Self { Self { @@ -101,10 +98,10 @@ where /// Helper for `change_offset` which should not get monomorphized as it /// doesn't refer to T or Self. - fn dyn_change_offset( + fn dyn_change_offset( mut area: Rect, mut offset: PageOffset, - source: &dyn ParagraphSource, + source: &dyn ParagraphSource<'_>, visible: &mut Vec, ) { visible.clear(); @@ -135,9 +132,9 @@ where /// Iterate over visible layouts (bounding box, style) together /// with corresponding string content. Should not get monomorphized. - fn foreach_visible<'a, S: StringType>( - source: &'a dyn ParagraphSource, - visible: &'a [TextLayoutProxy], + fn foreach_visible<'b>( + source: &'b dyn ParagraphSource<'a>, + visible: &'b [TextLayoutProxy], offset: PageOffset, func: &mut dyn FnMut(&TextLayout, &str), ) { @@ -146,13 +143,13 @@ where for par in offset.par..source.size() { let s = source.at(par, chr).content; - if s.as_ref().is_empty() { + if s.is_empty() { chr = 0; continue; } if let Some(layout_proxy) = vis_iter.next() { let layout = layout_proxy.layout(source); - func(&layout, s.as_ref()); + s.map(|t| func(&layout, t)); } else { break; } @@ -161,9 +158,9 @@ where } } -impl Component for Paragraphs +impl<'a, T> Component for Paragraphs where - T: ParagraphSource, + T: ParagraphSource<'a>, { type Msg = Never; @@ -197,9 +194,9 @@ where } } -impl Paginate for Paragraphs +impl<'a, T> Paginate for Paragraphs where - T: ParagraphSource, + T: ParagraphSource<'a>, { fn page_count(&mut self) -> usize { // There's always at least one page. @@ -223,7 +220,7 @@ pub mod trace { use super::*; - impl crate::trace::Trace for Paragraphs { + impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Paragraphs { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.string("component", "Paragraphs".into()); t.in_list("paragraphs", &|par_list| { @@ -247,9 +244,9 @@ pub mod trace { } #[derive(Clone, Copy)] -pub struct Paragraph { +pub struct Paragraph<'a> { /// Paragraph text. - content: T, + content: TString<'a>, /// Paragraph style. style: &'static TextStyle, /// Paragraph alignment. @@ -263,10 +260,10 @@ pub struct Paragraph { padding_bottom: i16, } -impl Paragraph { - pub const fn new(style: &'static TextStyle, content: T) -> Self { +impl<'a> Paragraph<'a> { + pub fn new>>(style: &'static TextStyle, content: T) -> Self { Self { - content, + content: content.into(), style, align: Alignment::Start, break_after: false, @@ -301,25 +298,17 @@ impl Paragraph { self } - pub fn content(&self) -> &T { + pub fn content(&self) -> &TString<'a> { &self.content } - pub fn update(&mut self, content: T) { - self.content = content + pub fn update>>(&mut self, content: T) { + self.content = content.into() } - /// Copy style and replace content. - pub fn map(&self, func: impl FnOnce(&T) -> U) -> Paragraph { - Paragraph { - content: func(&self.content), - style: self.style, - align: self.align, - break_after: self.break_after, - no_break: self.no_break, - padding_top: self.padding_top, - padding_bottom: self.padding_bottom, - } + pub fn skip_prefix(&self, offset: usize) -> Paragraph<'a> { + let content = self.content.skip_prefix(offset); + Paragraph { content, ..*self } } fn layout(&self, area: Rect) -> TextLayout { @@ -343,7 +332,7 @@ impl TextLayoutProxy { Self { offset, bounds } } - fn layout(&self, source: &dyn ParagraphSource) -> TextLayout { + fn layout(&self, source: &dyn ParagraphSource<'_>) -> TextLayout { let content = source.at(self.offset.par, self.offset.chr); let mut layout = content.layout(self.bounds); layout.continues_from_prev_page = self.offset.chr > 0; @@ -382,16 +371,16 @@ impl PageOffset { /// /// If the returned remaining area is not None then it holds that /// `next_offset.par == self.par + 1`. - fn advance( + fn advance( mut self, area: Rect, - source: &dyn ParagraphSource, + source: &dyn ParagraphSource<'_>, full_height: i16, ) -> (PageOffset, Option, Option) { let paragraph = source.at(self.par, self.chr); // Skip empty paragraphs. - if paragraph.content.as_ref().is_empty() { + if paragraph.content().is_empty() { self.par += 1; self.chr = 0; return (self, Some(area), None); @@ -416,7 +405,7 @@ impl PageOffset { // Find out the dimensions of the paragraph at given char offset. let mut layout = paragraph.layout(area); layout.continues_from_prev_page = self.chr > 0; - let fit = layout.fit_text(paragraph.content.as_ref()); + let fit = paragraph.content().map(|t| layout.fit_text(t)); let (used, remaining_area) = area.split_top(fit.height()); let layout = TextLayoutProxy::new(self, used); @@ -447,9 +436,9 @@ impl PageOffset { ) } - fn should_place_pair_on_next_page( - this_paragraph: &Paragraph, - next_paragraph: &Paragraph, + fn should_place_pair_on_next_page( + this_paragraph: &Paragraph<'_>, + next_paragraph: &Paragraph<'_>, area: Rect, full_height: i16, ) -> bool { @@ -461,13 +450,11 @@ impl PageOffset { let full_area = area.with_height(full_height); let key_height = this_paragraph - .layout(full_area) - .fit_text(this_paragraph.content.as_ref()) - .height(); + .content() + .map(|t| this_paragraph.layout(full_area).fit_text(t).height()); let val_height = next_paragraph - .layout(full_area) - .fit_text(next_paragraph.content.as_ref()) - .height(); + .content() + .map(|t| next_paragraph.layout(full_area).fit_text(t).height()); let screen_full_threshold = this_paragraph.style.text_font.line_height() + next_paragraph.style.text_font.line_height(); @@ -497,10 +484,10 @@ struct PageBreakIterator<'a, T> { current: Option, } -impl PageBreakIterator<'_, T> { - fn dyn_next( +impl<'a, T: ParagraphSource<'a>> PageBreakIterator<'_, T> { + fn dyn_next( mut area: Rect, - paragraphs: &dyn ParagraphSource, + paragraphs: &dyn ParagraphSource<'_>, mut offset: PageOffset, ) -> Option { let full_height = area.height(); @@ -527,7 +514,7 @@ impl PageBreakIterator<'_, T> { /// Yields indices to beginnings of successive pages. First value is always /// `PageOffset { 0, 0 }` even if the paragraph vector is empty. -impl Iterator for PageBreakIterator<'_, T> { +impl<'a, T: ParagraphSource<'a>> Iterator for PageBreakIterator<'_, T> { /// `PageOffset` denotes the first paragraph that is rendered and a /// character offset in that paragraph. type Item = PageOffset; @@ -608,9 +595,9 @@ impl Checklist { } } -impl Component for Checklist +impl<'a, T> Component for Checklist where - T: ParagraphSource, + T: ParagraphSource<'a>, { type Msg = Never; @@ -652,9 +639,9 @@ where } } -impl Paginate for Checklist +impl<'a, T> Paginate for Checklist where - T: ParagraphSource, + T: ParagraphSource<'a>, { fn page_count(&mut self) -> usize { 1 @@ -664,7 +651,7 @@ where } #[cfg(feature = "ui_debug")] -impl crate::trace::Trace for Checklist { +impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Checklist { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.component("Checklist"); t.int("current", self.current as i64); @@ -672,16 +659,13 @@ impl crate::trace::Trace for Checklist { } } -pub trait VecExt { - fn add(&mut self, paragraph: Paragraph) -> &mut Self; +pub trait VecExt<'a> { + fn add(&mut self, paragraph: Paragraph<'a>) -> &mut Self; } -impl VecExt for Vec, N> -where - T: AsRef, -{ - fn add(&mut self, paragraph: Paragraph) -> &mut Self { - if paragraph.content.as_ref().is_empty() { +impl<'a, const N: usize> VecExt<'a> for Vec, N> { + fn add(&mut self, paragraph: Paragraph<'a>) -> &mut Self { + if paragraph.content().is_empty() { return self; } if self.push(paragraph).is_err() { @@ -692,12 +676,10 @@ where } } -impl ParagraphSource for Vec, N> { - type StrType = T; - - fn at(&self, index: usize, offset: usize) -> Paragraph { +impl<'a, const N: usize> ParagraphSource<'a> for Vec, N> { + fn at(&self, index: usize, offset: usize) -> Paragraph<'a> { let para = &self[index]; - para.map(|content| content.skip_prefix(offset)) + para.skip_prefix(offset) } fn size(&self) -> usize { @@ -705,12 +687,10 @@ impl ParagraphSource for Vec, N> { } } -impl ParagraphSource for [Paragraph; N] { - type StrType = T; - - fn at(&self, index: usize, offset: usize) -> Paragraph { +impl<'a, const N: usize> ParagraphSource<'a> for [Paragraph<'a>; N] { + fn at(&self, index: usize, offset: usize) -> Paragraph<'a> { let para = &self[index]; - para.map(|content| content.skip_prefix(offset)) + para.skip_prefix(offset) } fn size(&self) -> usize { @@ -718,12 +698,10 @@ impl ParagraphSource for [Paragraph; N] { } } -impl ParagraphSource for Paragraph { - type StrType = T; - - fn at(&self, index: usize, offset: usize) -> Paragraph { +impl<'a> ParagraphSource<'a> for Paragraph<'a> { + fn at(&self, index: usize, offset: usize) -> Paragraph<'a> { assert_eq!(index, 0); - self.map(|content| content.skip_prefix(offset)) + self.skip_prefix(offset) } fn size(&self) -> usize { diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 612faeb326..9f7cb0e739 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -389,7 +389,7 @@ extern "C" fn ui_layout_progress_event(n_args: usize, args: *const Obj) -> Obj { let this: Gc = args[0].try_into()?; let value: u16 = args[1].try_into()?; let description: StrBuffer = args[2].try_into()?; - let msg = this.obj_event(Event::Progress(value, description.as_ref()))?; + let msg = this.obj_event(Event::Progress(value, description.into()))?; Ok(msg) }; unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) } diff --git a/core/embed/rust/src/ui/layout/util.rs b/core/embed/rust/src/ui/layout/util.rs index 0b2f12a69a..1759de7078 100644 --- a/core/embed/rust/src/ui/layout/util.rs +++ b/core/embed/rust/src/ui/layout/util.rs @@ -8,7 +8,6 @@ use crate::{ util::{iter_into_array, try_or_raise}, }, storage::{get_avatar_len, load_avatar}, - strutil::SkipPrefix, ui::{ component::text::{ paragraphs::{Paragraph, ParagraphSource}, @@ -62,10 +61,8 @@ pub struct ConfirmBlob { pub data_font: &'static TextStyle, } -impl ParagraphSource for ConfirmBlob { - type StrType = StrBuffer; - - fn at(&self, index: usize, offset: usize) -> Paragraph { +impl ParagraphSource<'static> for ConfirmBlob { + fn at(&self, index: usize, offset: usize) -> Paragraph<'static> { match index { 0 => Paragraph::new(self.description_font, self.description.skip_prefix(offset)), 1 => Paragraph::new(self.extra_font, self.extra.skip_prefix(offset)), @@ -102,10 +99,8 @@ impl PropsList { } } -impl ParagraphSource for PropsList { - type StrType = StrBuffer; - - fn at(&self, index: usize, offset: usize) -> Paragraph { +impl ParagraphSource<'static> for PropsList { + fn at(&self, index: usize, offset: usize) -> Paragraph<'static> { let block = move || { let entry = self.items.get(index / 2)?; let [key, value, value_is_mono]: [Obj; 3] = iter_into_array(entry)?; diff --git a/core/embed/rust/src/ui/model_tr/component/address_details.rs b/core/embed/rust/src/ui/model_tr/component/address_details.rs index 436c1e7fd7..45b73d80da 100644 --- a/core/embed/rust/src/ui/model_tr/component/address_details.rs +++ b/core/embed/rust/src/ui/model_tr/component/address_details.rs @@ -22,8 +22,8 @@ const QR_BORDER: i16 = 3; pub struct AddressDetails { qr_code: Qr, - details_view: Paragraphs>, - xpub_view: Frame>, StrBuffer>, + details_view: Paragraphs>, + xpub_view: Frame>, StrBuffer>, xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>, current_page: usize, current_subpage: usize, @@ -43,16 +43,13 @@ impl AddressDetails { let details_view = { let mut para = ParagraphVecShort::new(); if let Some(account) = account { - para.add(Paragraph::new( - &theme::TEXT_BOLD, - TR::words__account_colon.try_into()?, - )); + para.add(Paragraph::new(&theme::TEXT_BOLD, TR::words__account_colon)); para.add(Paragraph::new(&theme::TEXT_MONO, account)); } if let Some(path) = path { para.add(Paragraph::new( &theme::TEXT_BOLD, - TR::address_details__derivation_path.try_into()?, + TR::address_details__derivation_path, )); para.add(Paragraph::new(&theme::TEXT_MONO, path)); } @@ -60,7 +57,7 @@ impl AddressDetails { }; let xpub_view = Frame::new( "".into(), - Paragraph::new(&theme::TEXT_MONO_DATA, "".into()).into_paragraphs(), + Paragraph::new(&theme::TEXT_MONO_DATA, "").into_paragraphs(), ); let result = Self { diff --git a/core/embed/rust/src/ui/model_tr/component/flow_pages.rs b/core/embed/rust/src/ui/model_tr/component/flow_pages.rs index 25eeb91e81..4ccfef854b 100644 --- a/core/embed/rust/src/ui/model_tr/component/flow_pages.rs +++ b/core/embed/rust/src/ui/model_tr/component/flow_pages.rs @@ -78,7 +78,7 @@ pub struct Page where T: StringType + Clone, { - formatted: FormattedText, + formatted: FormattedText, btn_layout: ButtonLayout, btn_actions: ButtonActions, current_page: usize, @@ -95,7 +95,7 @@ where pub fn new( btn_layout: ButtonLayout, btn_actions: ButtonActions, - formatted: FormattedText, + formatted: FormattedText, ) -> Self { let mut page = Self { formatted, diff --git a/core/embed/rust/src/ui/model_tr/component/loader.rs b/core/embed/rust/src/ui/model_tr/component/loader.rs index 5ccb065956..8016576cb4 100644 --- a/core/embed/rust/src/ui/model_tr/component/loader.rs +++ b/core/embed/rust/src/ui/model_tr/component/loader.rs @@ -274,7 +274,7 @@ where pub fn start(&mut self, ctx: &mut EventCtx) { self.start_time = Some(Instant::now()); - self.loader.event(ctx, Event::Progress(0, "")); + self.loader.event(ctx, Event::Progress(0, "".into())); self.loader.mutate(ctx, |ctx, loader| { loader.request_paint(ctx); }); @@ -318,7 +318,7 @@ where let percentage = self.percentage(now); let new_loader_value = (percentage * LOADER_MAX as u32) / 100; self.loader - .event(ctx, Event::Progress(new_loader_value as u16, "")); + .event(ctx, Event::Progress(new_loader_value as u16, "".into())); // Returning only after the loader was fully painted if percentage >= 100 { return Some(LoaderMsg::GrownCompletely); diff --git a/core/embed/rust/src/ui/model_tr/component/progress.rs b/core/embed/rust/src/ui/model_tr/component/progress.rs index 2346eb7e23..d512dc75a3 100644 --- a/core/embed/rust/src/ui/model_tr/component/progress.rs +++ b/core/embed/rust/src/ui/model_tr/component/progress.rs @@ -1,7 +1,6 @@ use core::mem; use crate::{ - error::Error, strutil::StringType, ui::{ component::{ @@ -22,9 +21,6 @@ const BOTTOM_DESCRIPTION_MARGIN: i16 = 10; const LOADER_Y_OFFSET_TITLE: i16 = -10; const LOADER_Y_OFFSET_NO_TITLE: i16 = -20; -// Clippy was complaining about `very complex type used` -type UpdateDescriptionFn = fn(&str) -> Result; - pub struct Progress where T: StringType, @@ -33,9 +29,8 @@ where value: u16, loader_y_offset: i16, indeterminate: bool, - description: Child>>, + description: Child>>, description_pad: Pad, - update_description: Option>, icon: Icon, } @@ -55,7 +50,6 @@ where Paragraph::new(&theme::TEXT_NORMAL, description).centered(), )), description_pad: Pad::with_background(theme::BG), - update_description: None, icon: theme::ICON_TICK_FAT, } } @@ -65,14 +59,6 @@ where self } - pub fn with_update_description( - mut self, - update_description: UpdateDescriptionFn, - ) -> Self { - self.update_description = Some(update_description); - self - } - pub fn with_icon(mut self, icon: Icon) -> Self { self.icon = icon; self @@ -105,10 +91,7 @@ where .inner() .inner() .content() - .as_ref() - .chars() - .filter(|c| *c == '\n') - .count() as i16; + .map(|t| t.chars().filter(|c| *c == '\n').count() as i16); 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 { @@ -140,21 +123,16 @@ where if mem::replace(&mut self.value, new_value) != new_value { self.request_paint(ctx); } - if let Some(update_description) = self.update_description { - self.description.mutate(ctx, |ctx, para| { - // NOTE: not doing any change for empty new descriptions - // (currently, there is no use-case for deleting the description) - if !new_description.is_empty() - && para.inner_mut().content().as_ref() != new_description - { - let new_description = unwrap!((update_description)(new_description)); - para.inner_mut().update(new_description); - para.change_page(0); // Recompute bounding box. - ctx.request_paint(); - self.description_pad.clear(); - } - }); - } + self.description.mutate(ctx, |ctx, para| { + // NOTE: not doing any change for empty new descriptions + // (currently, there is no use-case for deleting the description) + if !new_description.is_empty() && para.inner_mut().content() != &new_description { + para.inner_mut().update(new_description); + para.change_page(0); // Recompute bounding box. + ctx.request_paint(); + self.description_pad.clear(); + } + }); } None } diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index dc17992b38..d949f72943 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -77,9 +77,9 @@ where } } -impl ComponentMsgObj for Paragraphs +impl<'a, T> ComponentMsgObj for Paragraphs where - T: ParagraphSource, + T: ParagraphSource<'a>, { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!() @@ -442,12 +442,12 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs: let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let ops = OpTextLayout::::new(theme::TEXT_NORMAL) - .text_normal(TR::reset__by_continuing.try_into()?) + let ops = OpTextLayout::new(theme::TEXT_NORMAL) + .text_normal(TR::reset__by_continuing) .next_page() - .text_normal(TR::reset__more_info_at.try_into()?) + .text_normal(TR::reset__more_info_at) .newline() - .text_bold(TR::reset__tos_link.try_into()?); + .text_bold(TR::reset__tos_link); let formatted = FormattedText::new(ops).vertically_centered(); content_in_button_page(title, formatted, button, Some("".into()), false) @@ -459,27 +459,24 @@ extern "C" fn new_confirm_backup(n_args: usize, args: *const Obj, kwargs: *mut M 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_new_wallet_created: StrBuffer = TR::backup__new_wallet_created.try_into()?; - let tr_it_should_be_backed_up_now: StrBuffer = - TR::backup__it_should_be_backed_up_now.try_into()?; let tr_title_backup_wallet: StrBuffer = TR::backup__title_backup_wallet.try_into()?; - let tr_recover_anytime: StrBuffer = TR::backup__recover_anytime.try_into()?; let get_page = move |page_index| match page_index { 0 => { let btn_layout = ButtonLayout::text_none_arrow_wide(TR::buttons__skip.into()); let btn_actions = ButtonActions::cancel_none_next(); let ops = OpTextLayout::new(theme::TEXT_NORMAL) - .text_normal(tr_new_wallet_created) + .text_normal(TR::backup__new_wallet_created) .newline() - .text_normal(tr_it_should_be_backed_up_now); + .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) } 1 => { let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__back_up.into()); let btn_actions = ButtonActions::prev_none_confirm(); - let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(tr_recover_anytime); + let ops = + OpTextLayout::new(theme::TEXT_NORMAL).text_normal(TR::backup__recover_anytime); let formatted = FormattedText::new(ops).vertically_centered(); Page::::new(btn_layout, btn_actions, formatted) .with_title(tr_title_backup_wallet) @@ -550,15 +547,9 @@ extern "C" fn new_confirm_joint_total(n_args: usize, args: *const Obj, kwargs: * let total_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_amount)?.try_into()?; let paragraphs = Paragraphs::new([ - Paragraph::new( - &theme::TEXT_BOLD, - TR::joint__you_are_contributing.try_into()?, - ), + Paragraph::new(&theme::TEXT_BOLD, TR::joint__you_are_contributing), Paragraph::new(&theme::TEXT_MONO, spending_amount), - Paragraph::new( - &theme::TEXT_BOLD, - TR::joint__to_the_total_amount.try_into()?, - ), + Paragraph::new(&theme::TEXT_BOLD, TR::joint__to_the_total_amount), Paragraph::new(&theme::TEXT_MONO, total_amount), ]); @@ -586,9 +577,9 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: }; let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, description.try_into()?), + Paragraph::new(&theme::TEXT_NORMAL, description), Paragraph::new(&theme::TEXT_MONO, amount_change).break_after(), - Paragraph::new(&theme::TEXT_BOLD, TR::modify_amount__new_amount.try_into()?), + Paragraph::new(&theme::TEXT_BOLD, TR::modify_amount__new_amount), Paragraph::new(&theme::TEXT_MONO, amount_new), ]); @@ -677,12 +668,6 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma let total_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_label)?.try_into()?; let fee_label: StrBuffer = kwargs.get(Qstr::MP_QSTR_fee_label)?.try_into()?; - // cached allocated translated strings that get_page can reuse - let tr_title_fee = TR::confirm_total__title_fee.try_into()?; - let tr_fee_rate = TR::confirm_total__fee_rate.try_into()?; - let tr_title_sending_from = TR::confirm_total__title_sending_from.try_into()?; - let tr_account = TR::words__account_colon.try_into()?; - let get_page = move |page_index| { match page_index { 0 => { @@ -701,7 +686,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::new(btn_layout, btn_actions, formatted) + Page::::new(btn_layout, btn_actions, formatted) } 1 => { // Fee rate info @@ -711,11 +696,11 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma let fee_rate_amount = fee_rate_amount.unwrap_or_default(); let ops = OpTextLayout::new(theme::TEXT_MONO) - .text_bold(tr_title_fee) + .text_bold(TR::confirm_total__title_fee) .newline() .newline() .newline_half() - .text_bold(tr_fee_rate) + .text_bold(TR::confirm_total__fee_rate) .newline() .text_mono(fee_rate_amount); @@ -732,11 +717,11 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma // TODO: include wallet info when available let ops = OpTextLayout::new(theme::TEXT_MONO) - .text_bold(tr_title_sending_from) + .text_bold(TR::confirm_total__title_sending_from) .newline() .newline() .newline_half() - .text_bold(tr_account) + .text_bold(TR::words__account_colon) .newline() .text_mono(account_label); @@ -802,9 +787,9 @@ extern "C" fn new_altcoin_tx_summary(n_args: usize, args: *const Obj, kwargs: *m ops = ops.next_page(); } ops = ops - .text_bold(unwrap!(key.try_into())) + .text_bold(unwrap!(TString::try_from(key))) .newline() - .text_mono(unwrap!(value.try_into())); + .text_mono(unwrap!(TString::try_from(value))); } let formatted = FormattedText::new(ops).vertically_centered(); @@ -858,11 +843,11 @@ extern "C" fn new_confirm_address(n_args: usize, args: *const Obj, kwargs: *mut /// (title, text, btn_layout, btn_actions, text_y_offset) fn tutorial_screen( title: StrBuffer, - text: StrBuffer, + text: TR, btn_layout: ButtonLayout, btn_actions: ButtonActions, ) -> Page { - let ops = OpTextLayout::::new(theme::TEXT_NORMAL).text_normal(text); + 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) } @@ -873,19 +858,12 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj // cached allocated translated strings that get_page can reuse let tr_title_hello: StrBuffer = TR::tutorial__title_hello.try_into()?; - let tr_welcome_press_right: StrBuffer = TR::tutorial__welcome_press_right.try_into()?; - let tr_use_trezor: StrBuffer = TR::tutorial__use_trezor.try_into()?; let tr_hold_to_confirm: StrBuffer = TR::buttons__hold_to_confirm.try_into()?; - let tr_press_and_hold: StrBuffer = TR::tutorial__press_and_hold.try_into()?; let tr_title_screen_scroll: StrBuffer = TR::tutorial__title_screen_scroll.try_into()?; - let tr_scroll_down: StrBuffer = TR::tutorial__scroll_down.try_into()?; let tr_confirm: StrBuffer = TR::buttons__confirm.try_into()?; - let tr_middle_click: StrBuffer = TR::tutorial__middle_click.try_into()?; let tr_title_tutorial_complete: StrBuffer = TR::tutorial__title_tutorial_complete.try_into()?; - let tr_ready_to_use: StrBuffer = TR::tutorial__ready_to_use.try_into()?; let tr_title_skip: StrBuffer = TR::tutorial__title_skip.try_into()?; - let tr_sure_you_want_skip: StrBuffer = TR::tutorial__sure_you_want_skip.try_into()?; let get_page = move |page_index| { // Lazy-loaded list of screens to show, with custom content, @@ -897,37 +875,37 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj // title, text, btn_layout, btn_actions 0 => tutorial_screen( tr_title_hello, - tr_welcome_press_right, + TR::tutorial__welcome_press_right, ButtonLayout::cancel_none_arrow(), ButtonActions::last_none_next(), ), 1 => tutorial_screen( "".into(), - tr_use_trezor, + TR::tutorial__use_trezor, ButtonLayout::arrow_none_arrow(), ButtonActions::prev_none_next(), ), 2 => tutorial_screen( tr_hold_to_confirm, - tr_press_and_hold, + 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_scroll_down, + TR::tutorial__scroll_down, ButtonLayout::arrow_none_text(TR::buttons__continue.into()), ButtonActions::prev_none_next(), ), 4 => tutorial_screen( tr_confirm, - tr_middle_click, + TR::tutorial__middle_click, ButtonLayout::none_armed_none(TR::buttons__confirm.into()), ButtonActions::none_next_none(), ), 5 => tutorial_screen( tr_title_tutorial_complete, - tr_ready_to_use, + TR::tutorial__ready_to_use, ButtonLayout::text_none_text( TR::buttons__again.into(), TR::buttons__continue.into(), @@ -936,7 +914,7 @@ extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj ), 6 => tutorial_screen( tr_title_skip, - tr_sure_you_want_skip, + TR::tutorial__sure_you_want_skip, ButtonLayout::arrow_none_text(TR::buttons__skip.into()), ButtonActions::beginning_none_cancel(), ), @@ -976,23 +954,14 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m let mut paragraphs_vec = ParagraphVecShort::new(); paragraphs_vec - .add(Paragraph::new(&theme::TEXT_BOLD, description.try_into()?)) + .add(Paragraph::new(&theme::TEXT_BOLD, description)) .add(Paragraph::new(&theme::TEXT_MONO, change)) - .add( - Paragraph::new( - &theme::TEXT_BOLD, - TR::modify_fee__transaction_fee.try_into()?, - ) - .no_break(), - ) + .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__transaction_fee).no_break()) .add(Paragraph::new(&theme::TEXT_MONO, total_fee_new)); if let Some(fee_rate_amount) = fee_rate_amount { paragraphs_vec - .add( - Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__fee_rate.try_into()?) - .no_break(), - ) + .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__fee_rate).no_break()) .add(Paragraph::new(&theme::TEXT_MONO, fee_rate_amount)); } @@ -1010,7 +979,7 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m extern "C" fn new_multiple_pages_texts(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let verb: TString<'static> = kwargs.get(Qstr::MP_QSTR_verb)?.try_into()?; + let verb: TString = kwargs.get(Qstr::MP_QSTR_verb)?.try_into()?; let items: Gc = kwargs.get(Qstr::MP_QSTR_items)?.try_into()?; // Cache the page count so that we can move `items` into the closure. @@ -1021,7 +990,7 @@ extern "C" fn new_multiple_pages_texts(n_args: usize, args: *const Obj, kwargs: // the need of any allocation here in Rust. let get_page = move |page_index| { let item_obj = unwrap!(items.get(page_index)); - let text = unwrap!(item_obj.try_into()); + let text = unwrap!(TString::try_from(item_obj)); let (btn_layout, btn_actions) = if page_count == 1 { // There is only one page @@ -1052,7 +1021,7 @@ 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::new(btn_layout, btn_actions, formatted) + Page::::new(btn_layout, btn_actions, formatted) }; let pages = FlowPages::new(get_page, page_count); @@ -1076,7 +1045,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map // the need of any allocation here in Rust. let get_page = move |page_index| { let account_obj = unwrap!(accounts.get(page_index)); - let account = account_obj.try_into().unwrap_or_else(|_| "".into()); + let account = TString::try_from(account_obj).unwrap_or_else(|_| TString::empty()); let (btn_layout, btn_actions) = if page_count == 1 { // There is only one page @@ -1111,7 +1080,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map .text_bold(account); let formatted = FormattedText::new(ops); - Page::new(btn_layout, btn_actions, formatted) + Page::::new(btn_layout, btn_actions, formatted) }; let pages = FlowPages::new(get_page, page_count); @@ -1137,7 +1106,7 @@ extern "C" fn new_show_warning(n_args: usize, args: *const Obj, kwargs: *mut Map let btn_layout = ButtonLayout::none_armed_none(button); let btn_actions = ButtonActions::none_confirm_none(); - let mut ops = OpTextLayout::::new(theme::TEXT_NORMAL); + let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); ops = ops.alignment(geometry::Alignment::Center); if !warning.is_empty() { ops = ops.text_bold(warning).newline(); @@ -1146,7 +1115,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::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))?; @@ -1207,24 +1176,20 @@ extern "C" fn new_show_mismatch(n_args: usize, args: *const Obj, kwargs: *mut Ma let block = move |_args: &[Obj], kwargs: &Map| { let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - // cached allocated translated strings that get_page can reuse - let tr_contact_support_at = TR::addr_mismatch__contact_support_at.try_into()?; - let tr_support_url = TR::addr_mismatch__support_url.try_into()?; - let get_page = move |page_index| { assert!(page_index == 0); let btn_layout = ButtonLayout::arrow_none_text(TR::buttons__quit.into()); let btn_actions = ButtonActions::cancel_none_confirm(); - let ops = OpTextLayout::::new(theme::TEXT_NORMAL) + let ops = OpTextLayout::new(theme::TEXT_NORMAL) .text_bold(title) .newline() .newline_half() - .text_normal(tr_contact_support_at) + .text_normal(TR::addr_mismatch__contact_support_at) .newline() - .text_bold(tr_support_url); + .text_bold(TR::addr_mismatch__support_url); let formatted = FormattedText::new(ops); - Page::new(btn_layout, btn_actions, formatted) + Page::::new(btn_layout, btn_actions, formatted) }; let pages = FlowPages::new(get_page, 1); @@ -1258,7 +1223,7 @@ extern "C" fn new_confirm_with_info(n_args: usize, args: *const Obj, kwargs: *mu let obj = LayoutObj::new(Frame::new( title, - ShowMore::>>::new( + ShowMore::>::new( paragraphs.into_paragraphs(), verb_cancel, button, @@ -1302,10 +1267,9 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut // Decreasing bottom padding between paragraphs to fit one screen let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_rounds.try_into()?) - .with_bottom_padding(2), + Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_rounds).with_bottom_padding(2), Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_mining_fee.try_into()?) + Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_mining_fee) .with_bottom_padding(2) .no_break(), Paragraph::new(&theme::TEXT_MONO, max_feerate).with_bottom_padding(2), @@ -1498,11 +1462,11 @@ extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut paragraphs .add(Paragraph::new( &theme::TEXT_NORMAL, - TR::recovery__only_first_n_letters.try_into()?, + TR::recovery__only_first_n_letters, )) .add(Paragraph::new( &theme::TEXT_NORMAL, - TR::recovery__cursor_will_change.try_into()?, + TR::recovery__cursor_will_change, )); } @@ -1578,8 +1542,7 @@ 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).with_update_description(StrBuffer::alloc); + let mut progress = Progress::new(indeterminate, description); if let Some(title) = title { progress = progress.with_title(title); }; diff --git a/core/embed/rust/src/ui/model_tt/component/address_details.rs b/core/embed/rust/src/ui/model_tt/component/address_details.rs index e9ec605d80..4d6acd7a4e 100644 --- a/core/embed/rust/src/ui/model_tt/component/address_details.rs +++ b/core/embed/rust/src/ui/model_tt/component/address_details.rs @@ -20,8 +20,8 @@ const MAX_XPUBS: usize = 16; pub struct AddressDetails { qr_code: Frame, - details: Frame>, T>, - xpub_view: Frame>, T>, + details: Frame>, T>, + xpub_view: Frame>, T>, xpubs: Vec<(T, T), MAX_XPUBS>, xpub_page_count: Vec, current_page: usize, @@ -46,14 +46,14 @@ where if let Some(a) = account { para.add(Paragraph::new( &theme::TEXT_NORMAL, - TR::words__account_colon.try_into()?, + TR::words__account_colon, )); para.add(Paragraph::new(&theme::TEXT_MONO, a)); } if let Some(p) = path { para.add(Paragraph::new( &theme::TEXT_NORMAL, - TR::address_details__derivation_path.try_into()?, + TR::address_details__derivation_path, )); para.add(Paragraph::new(&theme::TEXT_MONO, p)); } @@ -75,7 +75,7 @@ where xpub_view: Frame::left_aligned( theme::label_title(), " \n ".into(), - Paragraph::new(&theme::TEXT_MONO, "".into()).into_paragraphs(), + Paragraph::new(&theme::TEXT_MONO, "").into_paragraphs(), ) .with_cancel_button() .with_border(theme::borders_horizontal_scroll()), diff --git a/core/embed/rust/src/ui/model_tt/component/dialog.rs b/core/embed/rust/src/ui/model_tt/component/dialog.rs index 2e7f409b67..85a46540dc 100644 --- a/core/embed/rust/src/ui/model_tt/component/dialog.rs +++ b/core/embed/rust/src/ui/model_tt/component/dialog.rs @@ -1,5 +1,5 @@ use crate::{ - strutil::StringType, + strutil::TString, ui::{ component::{ image::BlendedImage, @@ -91,18 +91,17 @@ where } } -pub struct IconDialog { +pub struct IconDialog { image: Child, - paragraphs: Paragraphs>, + paragraphs: Paragraphs>, controls: Child, } -impl IconDialog +impl IconDialog where - T: StringType, U: Component, { - pub fn new(icon: BlendedImage, title: T, controls: U) -> Self { + pub fn new(icon: BlendedImage, title: impl Into>, controls: U) -> Self { Self { image: Child::new(icon), paragraphs: Paragraphs::new(ParagraphVecShort::from_iter([Paragraph::new( @@ -119,26 +118,26 @@ where } } - pub fn with_paragraph(mut self, para: Paragraph) -> Self { - if !para.content().as_ref().is_empty() { + pub fn with_paragraph(mut self, para: Paragraph<'static>) -> Self { + if !para.content().is_empty() { self.paragraphs.inner_mut().add(para); } self } - pub fn with_text(self, style: &'static TextStyle, text: T) -> Self { + pub fn with_text(self, style: &'static TextStyle, text: impl Into>) -> Self { self.with_paragraph(Paragraph::new(style, text).centered()) } - pub fn with_description(self, description: T) -> Self { + pub fn with_description(self, description: impl Into>) -> Self { self.with_text(&theme::TEXT_NORMAL_OFF_WHITE, description) } - pub fn with_value(self, value: T) -> Self { + pub fn with_value(self, value: impl Into>) -> Self { self.with_text(&theme::TEXT_MONO, value) } - pub fn new_shares(lines: [T; 4], controls: U) -> Self { + pub fn new_shares(lines: [impl Into>; 4], controls: U) -> Self { let [l0, l1, l2, l3] = lines; Self { image: Child::new(BlendedImage::new( @@ -165,9 +164,8 @@ where pub const VALUE_SPACE: i16 = 5; } -impl Component for IconDialog +impl Component for IconDialog where - T: StringType, U: Component, { type Msg = DialogMsg; @@ -207,9 +205,8 @@ where } #[cfg(feature = "ui_debug")] -impl crate::trace::Trace for IconDialog +impl crate::trace::Trace for IconDialog where - T: StringType, U: crate::trace::Trace, { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/model_tt/component/number_input.rs b/core/embed/rust/src/ui/model_tt/component/number_input.rs index e5745870a8..702cc156fa 100644 --- a/core/embed/rust/src/ui/model_tt/component/number_input.rs +++ b/core/embed/rust/src/ui/model_tt/component/number_input.rs @@ -29,7 +29,7 @@ where area: Rect, description_func: F, input: Child, - paragraphs: Child>>, + paragraphs: Child>>, paragraphs_pad: Pad, info_button: Child>, confirm_button: Child>, diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index 5b874d6c99..34f9213fc8 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -485,16 +485,13 @@ impl PageLayout { #[cfg(test)] mod tests { - use serde_json; - use crate::{ - strutil::SkipPrefix, trace::tests::trace, ui::{ component::text::paragraphs::{Paragraph, Paragraphs}, event::TouchEvent, geometry::Point, - model_tt::{constant, theme}, + model_tt::constant, }, }; @@ -502,12 +499,6 @@ mod tests { const SCREEN: Rect = constant::screen().inset(theme::borders()); - impl SkipPrefix for &str { - fn skip_prefix(&self, chars: usize) -> Self { - &self[chars..] - } - } - fn swipe(component: &mut impl Component, points: &[(i16, i16)]) { let last = points.len().saturating_sub(1); let mut first = true; @@ -538,7 +529,7 @@ mod tests { #[test] fn paragraphs_empty() { let mut page = ButtonPage::<_, &'static str>::new( - Paragraphs::<[Paragraph<&'static str>; 0]>::new([]), + Paragraphs::<[Paragraph<'static>; 0]>::new([]), theme::BG, ); page.place(SCREEN); diff --git a/core/embed/rust/src/ui/model_tt/component/progress.rs b/core/embed/rust/src/ui/model_tt/component/progress.rs index c6083ff75b..ad6d8dd561 100644 --- a/core/embed/rust/src/ui/model_tt/component/progress.rs +++ b/core/embed/rust/src/ui/model_tt/component/progress.rs @@ -1,8 +1,7 @@ use core::mem; use crate::{ - error::Error, - strutil::StringType, + strutil::{StringType, TString}, ui::{ component::{ base::ComponentExt, @@ -24,9 +23,8 @@ pub struct Progress { value: u16, loader_y_offset: i16, indeterminate: bool, - description: Child>>, + description: Child>>, description_pad: Pad, - update_description: fn(&str) -> Result, } impl Progress @@ -35,12 +33,7 @@ where { const AREA: Rect = constant::screen().inset(theme::borders()); - pub fn new( - title: T, - indeterminate: bool, - description: T, - update_description: fn(&str) -> Result, - ) -> Self { + pub fn new(title: T, indeterminate: bool, description: TString<'static>) -> Self { Self { title: Label::centered(title, theme::label_progress()).into_child(), value: 0, @@ -51,7 +44,6 @@ where ) .into_child(), description_pad: Pad::with_background(theme::BG), - update_description, } } } @@ -68,10 +60,7 @@ where .inner() .inner() .content() - .as_ref() - .chars() - .filter(|c| *c == '\n') - .count() as i16; + .map(|t| t.chars().filter(|c| *c == '\n').count() as i16); let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y); let (loader, description) = rest.split_bottom(Font::NORMAL.line_height() * description_lines); @@ -90,8 +79,7 @@ where ctx.request_paint(); } self.description.mutate(ctx, |ctx, para| { - if para.inner_mut().content().as_ref() != new_description { - let new_description = unwrap!((self.update_description)(new_description)); + if para.inner_mut().content() != &new_description { para.inner_mut().update(new_description); para.change_page(0); // Recompute bounding box. ctx.request_paint(); diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 712a08b412..c7e5a075c5 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -128,9 +128,8 @@ where } } -impl ComponentMsgObj for IconDialog +impl ComponentMsgObj for IconDialog where - T: StringType, U: Component, ::Msg: TryInto, { @@ -223,27 +222,24 @@ where // Clippy/compiler complains about conflicting implementations // TODO move the common impls to a common module #[cfg(not(feature = "clippy"))] -impl ComponentMsgObj for Paragraphs +impl<'a, T> ComponentMsgObj for Paragraphs where - T: ParagraphSource, + T: ParagraphSource<'a>, { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!() } } -impl ComponentMsgObj for FormattedText -where - T: StringType + Clone, -{ +impl ComponentMsgObj for FormattedText { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!() } } -impl ComponentMsgObj for Checklist +impl<'a, T> ComponentMsgObj for Checklist where - T: ParagraphSource, + T: ParagraphSource<'a>, { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!() @@ -298,10 +294,9 @@ impl ComponentMsgObj for Lockscreen { } } -impl ComponentMsgObj for (GridPlaced>, GridPlaced>) +impl<'a, T> ComponentMsgObj for (GridPlaced>, GridPlaced) where - T: ParagraphSource, - S: StringType + Clone, + T: ParagraphSource<'a>, { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!() @@ -429,7 +424,7 @@ extern "C" fn new_confirm_emphasized(n_args: usize, args: *const Obj, kwargs: *m let mut ops = OpTextLayout::new(theme::TEXT_NORMAL); for item in IterBuf::new().try_iterate(items)? { if item.is_str() { - ops = ops.text_normal(item.try_into()?) + ops = ops.text_normal(StrBuffer::try_from(item)?) } else { let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?; let text: StrBuffer = text.try_into()?; @@ -688,11 +683,10 @@ extern "C" fn new_confirm_reset_device(n_args: usize, args: *const Obj, kwargs: let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let par_array: [Paragraph; 3] = [ - Paragraph::new(&theme::TEXT_NORMAL, TR::reset__by_continuing.try_into()?) - .with_bottom_padding(17), // simulating a carriage return - Paragraph::new(&theme::TEXT_NORMAL, TR::reset__more_info_at.try_into()?), - Paragraph::new(&theme::TEXT_DEMIBOLD, TR::reset__tos_link.try_into()?), + let par_array: [Paragraph<'static>; 3] = [ + Paragraph::new(&theme::TEXT_NORMAL, TR::reset__by_continuing).with_bottom_padding(17), /* simulating a carriage return */ + Paragraph::new(&theme::TEXT_NORMAL, TR::reset__more_info_at), + Paragraph::new(&theme::TEXT_DEMIBOLD, TR::reset__tos_link), ]; let paragraphs = Paragraphs::new(par_array); let buttons = Button::cancel_confirm( @@ -855,18 +849,15 @@ extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: let amount_new: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?; let description = if sign < 0 { - TR::modify_amount__decrease_amount.try_into()? + TR::modify_amount__decrease_amount } else { - TR::modify_amount__increase_amount.try_into()? + TR::modify_amount__increase_amount }; let paragraphs = Paragraphs::new([ Paragraph::new(&theme::TEXT_NORMAL, description), Paragraph::new(&theme::TEXT_MONO, amount_change), - Paragraph::new( - &theme::TEXT_NORMAL, - TR::modify_amount__new_amount.try_into()?, - ), + Paragraph::new(&theme::TEXT_NORMAL, TR::modify_amount__new_amount), Paragraph::new(&theme::TEXT_MONO, amount_new), ]); @@ -891,19 +882,19 @@ extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *m let (description, change, total_label) = match sign { s if s < 0 => ( - TR::modify_fee__decrease_fee.try_into()?, + TR::modify_fee__decrease_fee, user_fee_change, - TR::modify_fee__new_transaction_fee.try_into()?, + TR::modify_fee__new_transaction_fee, ), s if s > 0 => ( - TR::modify_fee__increase_fee.try_into()?, + TR::modify_fee__increase_fee, user_fee_change, - TR::modify_fee__new_transaction_fee.try_into()?, + TR::modify_fee__new_transaction_fee, ), _ => ( - TR::modify_fee__no_change.try_into()?, + TR::modify_fee__no_change, StrBuffer::empty(), - TR::modify_fee__transaction_fee.try_into()?, + TR::modify_fee__transaction_fee, ), }; @@ -1240,12 +1231,9 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?; let paragraphs = Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_rounds.try_into()?), + Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_rounds), Paragraph::new(&theme::TEXT_MONO, max_rounds), - Paragraph::new( - &theme::TEXT_NORMAL, - TR::coinjoin__max_mining_fee.try_into()?, - ), + Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_mining_fee), Paragraph::new(&theme::TEXT_MONO, max_feerate), ]); @@ -1482,9 +1470,9 @@ extern "C" fn new_select_word_count(n_args: usize, args: *const Obj, kwargs: *mu TR::recovery__title.try_into()? }; - let paragraphs = Paragraphs::new(Paragraph::::new( + let paragraphs = Paragraphs::new(Paragraph::new( &theme::TEXT_DEMIBOLD, - TR::recovery__select_num_of_words.try_into()?, + TR::recovery__select_num_of_words, )); let obj = LayoutObj::new(Frame::left_aligned( @@ -1562,12 +1550,7 @@ 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, - StrBuffer::alloc, - ))?; + let obj = LayoutObj::new(Progress::new(title, indeterminate, description.into()))?; Ok(obj.into()) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }