1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 15:38:11 +00:00

refactor(core/rust): Paragraph is now based on TString

This commit is contained in:
matejcik 2024-03-17 13:40:10 +01:00 committed by matejcik
parent 9c287adf64
commit 39b7b22777
19 changed files with 261 additions and 418 deletions

View File

@ -1,10 +1,6 @@
use core::{convert::TryFrom, ops::Deref, ptr, slice, str}; use core::{convert::TryFrom, ops::Deref, ptr, slice, str};
use crate::{ use crate::{error::Error, micropython::obj::Obj, strutil::hexlify};
error::Error,
micropython::obj::Obj,
strutil::{hexlify, SkipPrefix},
};
use super::ffi; use super::ffi;
@ -93,10 +89,8 @@ impl StrBuffer {
unsafe { slice::from_raw_parts(self.ptr.add(self.off.into()), self.len.into()) } unsafe { slice::from_raw_parts(self.ptr.add(self.off.into()), self.len.into()) }
} }
} }
}
impl SkipPrefix for StrBuffer { pub fn skip_prefix(&self, skip_bytes: usize) -> Self {
fn skip_prefix(&self, skip_bytes: usize) -> Self {
let off: u16 = unwrap!(skip_bytes.try_into()); let off: u16 = unwrap!(skip_bytes.try_into());
assert!(off <= self.len); assert!(off <= self.len);
assert!(self.as_ref().is_char_boundary(skip_bytes)); assert!(self.as_ref().is_char_boundary(skip_bytes));

View File

@ -9,38 +9,18 @@ use crate::micropython::{buffer::StrBuffer, obj::Obj};
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
use crate::translations::TR; use crate::translations::TR;
/// Trait for slicing off string prefix by a specified number of bytes. /// Trait for internal representation of strings. This is a legacy crutch before
/// See `StringType` for deeper explanation. /// we fully transition to `TString`. For now, it allows some manner of
pub trait SkipPrefix { /// compatibility between `&str` and `StrBuffer`. Implies the following
fn skip_prefix(&self, bytes: usize) -> Self; /// operations:
} /// - dereference into a short-lived `&str` reference (AsRef<str>) (probably not
/// strictly necessary anymore)
// 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<str>)
/// - create a new string by skipping some number of bytes (SkipPrefix) - used
/// when rendering continuations of long strings
/// - create a new string from a string literal (From<&'static str>) /// - create a new string from a string literal (From<&'static str>)
pub trait StringType: /// - infallibly convert into a `TString` (Into<TString<'static>>), which is
AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix /// then used for other operations.
{ pub trait StringType: AsRef<str> + From<&'static str> + Into<TString<'static>> {}
}
impl<T> StringType for T where impl<T> StringType for T where T: AsRef<str> + From<&'static str> + Into<TString<'static>> {}
T: AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix
{
}
/// Unified-length String type, long enough for most simple use-cases. /// Unified-length String type, long enough for most simple use-cases.
pub type ShortString = String<50>; pub type ShortString = String<50>;
@ -129,6 +109,23 @@ impl TString<'_> {
Self::Str(s) => fun(s), 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> { impl TString<'static> {
@ -198,22 +195,3 @@ impl<'a, 'b> PartialEq<TString<'a>> for TString<'b> {
} }
impl Eq for TString<'_> {} 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..]),
}
}
}

View File

@ -3,6 +3,7 @@ use core::mem;
use heapless::Vec; use heapless::Vec;
use crate::{ use crate::{
strutil::TString,
time::Duration, time::Duration,
ui::{ ui::{
component::{maybe::PaintOverlapping, MsgMap}, component::{maybe::PaintOverlapping, MsgMap},
@ -415,7 +416,7 @@ where
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum Event<'a> { pub enum Event {
#[cfg(feature = "button")] #[cfg(feature = "button")]
Button(ButtonEvent), Button(ButtonEvent),
#[cfg(feature = "touch")] #[cfg(feature = "touch")]
@ -425,7 +426,7 @@ pub enum Event<'a> {
/// token (another timer has to be requested). /// token (another timer has to be requested).
Timer(TimerToken), Timer(TimerToken),
/// Advance progress bar. Progress screens only. /// 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 /// Component has been attached to component tree. This event is sent once
/// before any other events. /// before any other events.
Attach, Attach,

View File

@ -1,9 +1,6 @@
use crate::{ use crate::ui::{
strutil::StringType, component::{Component, Event, EventCtx, Never, Paginate},
ui::{ geometry::{Alignment, Offset, Rect},
component::{Component, Event, EventCtx, Never, Paginate},
geometry::{Alignment, Offset, Rect},
},
}; };
use super::{ use super::{
@ -12,15 +9,15 @@ use super::{
}; };
#[derive(Clone)] #[derive(Clone)]
pub struct FormattedText<T: StringType + Clone> { pub struct FormattedText {
op_layout: OpTextLayout<T>, op_layout: OpTextLayout<'static>,
vertical: Alignment, vertical: Alignment,
char_offset: usize, char_offset: usize,
y_offset: i16, y_offset: i16,
} }
impl<T: StringType + Clone> FormattedText<T> { impl FormattedText {
pub fn new(op_layout: OpTextLayout<T>) -> Self { pub fn new(op_layout: OpTextLayout<'static>) -> Self {
Self { Self {
op_layout, op_layout,
vertical: Alignment::Start, vertical: Alignment::Start,
@ -54,7 +51,7 @@ impl<T: StringType + Clone> FormattedText<T> {
} }
// Pagination // Pagination
impl<T: StringType + Clone> Paginate for FormattedText<T> { impl Paginate for FormattedText {
fn page_count(&mut self) -> usize { fn page_count(&mut self) -> usize {
let mut page_count = 1; // There's always at least one page. let mut page_count = 1; // There's always at least one page.
@ -118,7 +115,7 @@ impl<T: StringType + Clone> Paginate for FormattedText<T> {
} }
} }
impl<T: StringType + Clone> Component for FormattedText<T> { impl Component for FormattedText {
type Msg = Never; type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -145,7 +142,7 @@ impl<T: StringType + Clone> Component for FormattedText<T> {
// DEBUG-ONLY SECTION BELOW // DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T: StringType + Clone> crate::trace::Trace for FormattedText<T> { impl crate::trace::Trace for FormattedText {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
use crate::ui::component::text::layout::trace::TraceSink; use crate::ui::component::text::layout::trace::TraceSink;
use core::cell::Cell; use core::cell::Cell;

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
strutil::StringType, strutil::TString,
ui::{ ui::{
display::{Color, Font}, display::{Color, Font},
geometry::{Alignment, Offset, Rect}, geometry::{Alignment, Offset, Rect},
@ -26,12 +26,12 @@ const PROCESSED_CHARS_ONE: usize = 1;
#[derive(Clone)] #[derive(Clone)]
/// Extension of TextLayout, allowing for Op-based operations /// Extension of TextLayout, allowing for Op-based operations
pub struct OpTextLayout<T: StringType + Clone> { pub struct OpTextLayout<'a> {
pub layout: TextLayout, pub layout: TextLayout,
ops: Vec<Op<T>, MAX_OPS>, ops: Vec<Op<'a>, MAX_OPS>,
} }
impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> { impl<'a> OpTextLayout<'a> {
pub fn new(style: TextStyle) -> Self { pub fn new(style: TextStyle) -> Self {
Self { Self {
layout: TextLayout::new(style), layout: TextLayout::new(style),
@ -116,7 +116,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
// (just for incomplete texts that were separated) // (just for incomplete texts that were separated)
layout.continues_from_prev_page = continued; 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 { match fit {
LayoutFit::Fitting { LayoutFit::Fitting {
@ -148,9 +148,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
/// Gets rid of all action-Ops that are before the `skip_bytes` threshold. /// 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 /// (Not removing the style changes, e.g. Font or Color, because they need
/// to be correctly set for future Text operations.) /// to be correctly set for future Text operations.)
fn filter_skipped_ops<'b, I>(ops_iter: I, skip_bytes: usize) -> impl Iterator<Item = Op<T>> + 'b fn filter_skipped_ops<'b, I>(
ops_iter: I,
skip_bytes: usize,
) -> impl Iterator<Item = Op<'a>> + 'b
where where
I: Iterator<Item = &'b Op<T>> + 'b, I: Iterator<Item = &'b Op<'a>> + 'b,
'a: 'b, 'a: 'b,
{ {
let mut skipped = 0; let mut skipped = 0;
@ -158,7 +161,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
match op { match op {
Op::Text(text, _continued) if skipped < skip_bytes => { Op::Text(text, _continued) if skipped < skip_bytes => {
let skip_text_bytes_if_fits_partially = skip_bytes - skipped; 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 { if skipped > skip_bytes {
// Fits partially // Fits partially
// Skipping some bytes at the beginning, leaving rest // Skipping some bytes at the beginning, leaving rest
@ -187,15 +190,15 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
} }
// Op-adding operations // Op-adding operations
impl<T: StringType + Clone> OpTextLayout<T> { impl<'a> OpTextLayout<'a> {
pub fn with_new_item(mut self, item: Op<T>) -> Self { pub fn with_new_item(mut self, item: Op<'a>) -> Self {
self.ops self.ops
.push(item) .push(item)
.assert_if_debugging_ui("Could not push to self.ops - increase MAX_OPS."); .assert_if_debugging_ui("Could not push to self.ops - increase MAX_OPS.");
self self
} }
pub fn text(self, text: T) -> Self { pub fn text(self, text: TString<'a>) -> Self {
self.with_new_item(Op::Text(text, false)) self.with_new_item(Op::Text(text, false))
} }
@ -237,21 +240,21 @@ impl<T: StringType + Clone> OpTextLayout<T> {
} }
// Op-adding aggregation operations // Op-adding aggregation operations
impl<T: StringType + Clone> OpTextLayout<T> { impl<'a> OpTextLayout<'a> {
pub fn text_normal(self, text: T) -> Self { pub fn text_normal(self, text: impl Into<TString<'a>>) -> Self {
self.font(Font::NORMAL).text(text) self.font(Font::NORMAL).text(text.into())
} }
pub fn text_mono(self, text: T) -> Self { pub fn text_mono(self, text: impl Into<TString<'a>>) -> Self {
self.font(Font::MONO).text(text) self.font(Font::MONO).text(text.into())
} }
pub fn text_bold(self, text: T) -> Self { pub fn text_bold(self, text: impl Into<TString<'a>>) -> Self {
self.font(Font::BOLD).text(text) self.font(Font::BOLD).text(text.into())
} }
pub fn text_demibold(self, text: T) -> Self { pub fn text_demibold(self, text: impl Into<TString<'a>>) -> Self {
self.font(Font::DEMIBOLD).text(text) self.font(Font::DEMIBOLD).text(text.into())
} }
pub fn chunkify_text(self, chunks: Option<(Chunks, i16)>) -> Self { pub fn chunkify_text(self, chunks: Option<(Chunks, i16)>) -> Self {
@ -264,11 +267,11 @@ impl<T: StringType + Clone> OpTextLayout<T> {
} }
#[derive(Clone)] #[derive(Clone)]
pub enum Op<T: StringType> { pub enum Op<'a> {
/// Render text with current color and font. /// Render text with current color and font.
/// Bool signifies whether this is a split Text Op continued from previous /// Bool signifies whether this is a split Text Op continued from previous
/// page. If true, a leading ellipsis will be rendered. /// page. If true, a leading ellipsis will be rendered.
Text(T, bool), Text(TString<'a>, bool),
/// Set current text color. /// Set current text color.
Color(Color), Color(Color),
/// Set currently used font. /// Set currently used font.

View File

@ -1,7 +1,7 @@
use heapless::Vec; use heapless::Vec;
use crate::{ use crate::{
strutil::StringType, strutil::TString,
ui::{ ui::{
component::{Component, Event, EventCtx, Never, Paginate}, component::{Component, Event, EventCtx, Never, Paginate},
display::toif::Icon, 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. /// Offset of paragraph bounding box bottom relative to bottom of its text.
pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5; pub const PARAGRAPH_BOTTOM_SPACE: i16 = 5;
pub type ParagraphVecLong<T> = Vec<Paragraph<T>, 32>; pub type ParagraphVecLong<'a> = Vec<Paragraph<'a>, 32>;
pub type ParagraphVecShort<T> = Vec<Paragraph<T>, 8>; pub type ParagraphVecShort<'a> = Vec<Paragraph<'a>, 8>;
pub trait ParagraphSource {
/// Determines the output type produced.
type StrType: StringType;
pub trait ParagraphSource<'a> {
/// Return text and associated style for given paragraph index and character /// Return text and associated style for given paragraph index and character
/// offset within the paragraph. /// offset within the paragraph.
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType>; fn at(&self, index: usize, offset: usize) -> Paragraph<'a>;
/// Number of paragraphs. /// Number of paragraphs.
fn size(&self) -> usize; fn size(&self) -> usize;
@ -56,9 +53,9 @@ pub struct Paragraphs<T> {
source: T, source: T,
} }
impl<T> Paragraphs<T> impl<'a, T> Paragraphs<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
pub fn new(source: T) -> Self { pub fn new(source: T) -> Self {
Self { Self {
@ -101,10 +98,10 @@ where
/// Helper for `change_offset` which should not get monomorphized as it /// Helper for `change_offset` which should not get monomorphized as it
/// doesn't refer to T or Self. /// doesn't refer to T or Self.
fn dyn_change_offset<S: StringType>( fn dyn_change_offset(
mut area: Rect, mut area: Rect,
mut offset: PageOffset, mut offset: PageOffset,
source: &dyn ParagraphSource<StrType = S>, source: &dyn ParagraphSource<'_>,
visible: &mut Vec<TextLayoutProxy, MAX_LINES>, visible: &mut Vec<TextLayoutProxy, MAX_LINES>,
) { ) {
visible.clear(); visible.clear();
@ -135,9 +132,9 @@ where
/// Iterate over visible layouts (bounding box, style) together /// Iterate over visible layouts (bounding box, style) together
/// with corresponding string content. Should not get monomorphized. /// with corresponding string content. Should not get monomorphized.
fn foreach_visible<'a, S: StringType>( fn foreach_visible<'b>(
source: &'a dyn ParagraphSource<StrType = S>, source: &'b dyn ParagraphSource<'a>,
visible: &'a [TextLayoutProxy], visible: &'b [TextLayoutProxy],
offset: PageOffset, offset: PageOffset,
func: &mut dyn FnMut(&TextLayout, &str), func: &mut dyn FnMut(&TextLayout, &str),
) { ) {
@ -146,13 +143,13 @@ where
for par in offset.par..source.size() { for par in offset.par..source.size() {
let s = source.at(par, chr).content; let s = source.at(par, chr).content;
if s.as_ref().is_empty() { if s.is_empty() {
chr = 0; chr = 0;
continue; continue;
} }
if let Some(layout_proxy) = vis_iter.next() { if let Some(layout_proxy) = vis_iter.next() {
let layout = layout_proxy.layout(source); let layout = layout_proxy.layout(source);
func(&layout, s.as_ref()); s.map(|t| func(&layout, t));
} else { } else {
break; break;
} }
@ -161,9 +158,9 @@ where
} }
} }
impl<T> Component for Paragraphs<T> impl<'a, T> Component for Paragraphs<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
type Msg = Never; type Msg = Never;
@ -197,9 +194,9 @@ where
} }
} }
impl<T> Paginate for Paragraphs<T> impl<'a, T> Paginate for Paragraphs<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
fn page_count(&mut self) -> usize { fn page_count(&mut self) -> usize {
// There's always at least one page. // There's always at least one page.
@ -223,7 +220,7 @@ pub mod trace {
use super::*; use super::*;
impl<T: ParagraphSource> crate::trace::Trace for Paragraphs<T> { impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Paragraphs<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.string("component", "Paragraphs".into()); t.string("component", "Paragraphs".into());
t.in_list("paragraphs", &|par_list| { t.in_list("paragraphs", &|par_list| {
@ -247,9 +244,9 @@ pub mod trace {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Paragraph<T> { pub struct Paragraph<'a> {
/// Paragraph text. /// Paragraph text.
content: T, content: TString<'a>,
/// Paragraph style. /// Paragraph style.
style: &'static TextStyle, style: &'static TextStyle,
/// Paragraph alignment. /// Paragraph alignment.
@ -263,10 +260,10 @@ pub struct Paragraph<T> {
padding_bottom: i16, padding_bottom: i16,
} }
impl<T> Paragraph<T> { impl<'a> Paragraph<'a> {
pub const fn new(style: &'static TextStyle, content: T) -> Self { pub fn new<T: Into<TString<'a>>>(style: &'static TextStyle, content: T) -> Self {
Self { Self {
content, content: content.into(),
style, style,
align: Alignment::Start, align: Alignment::Start,
break_after: false, break_after: false,
@ -301,25 +298,17 @@ impl<T> Paragraph<T> {
self self
} }
pub fn content(&self) -> &T { pub fn content(&self) -> &TString<'a> {
&self.content &self.content
} }
pub fn update(&mut self, content: T) { pub fn update<T: Into<TString<'a>>>(&mut self, content: T) {
self.content = content self.content = content.into()
} }
/// Copy style and replace content. pub fn skip_prefix(&self, offset: usize) -> Paragraph<'a> {
pub fn map<U>(&self, func: impl FnOnce(&T) -> U) -> Paragraph<U> { let content = self.content.skip_prefix(offset);
Paragraph { Paragraph { content, ..*self }
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,
}
} }
fn layout(&self, area: Rect) -> TextLayout { fn layout(&self, area: Rect) -> TextLayout {
@ -343,7 +332,7 @@ impl TextLayoutProxy {
Self { offset, bounds } Self { offset, bounds }
} }
fn layout<S: StringType>(&self, source: &dyn ParagraphSource<StrType = S>) -> TextLayout { fn layout(&self, source: &dyn ParagraphSource<'_>) -> TextLayout {
let content = source.at(self.offset.par, self.offset.chr); let content = source.at(self.offset.par, self.offset.chr);
let mut layout = content.layout(self.bounds); let mut layout = content.layout(self.bounds);
layout.continues_from_prev_page = self.offset.chr > 0; 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 /// If the returned remaining area is not None then it holds that
/// `next_offset.par == self.par + 1`. /// `next_offset.par == self.par + 1`.
fn advance<S: StringType>( fn advance(
mut self, mut self,
area: Rect, area: Rect,
source: &dyn ParagraphSource<StrType = S>, source: &dyn ParagraphSource<'_>,
full_height: i16, full_height: i16,
) -> (PageOffset, Option<Rect>, Option<TextLayoutProxy>) { ) -> (PageOffset, Option<Rect>, Option<TextLayoutProxy>) {
let paragraph = source.at(self.par, self.chr); let paragraph = source.at(self.par, self.chr);
// Skip empty paragraphs. // Skip empty paragraphs.
if paragraph.content.as_ref().is_empty() { if paragraph.content().is_empty() {
self.par += 1; self.par += 1;
self.chr = 0; self.chr = 0;
return (self, Some(area), None); return (self, Some(area), None);
@ -416,7 +405,7 @@ impl PageOffset {
// Find out the dimensions of the paragraph at given char offset. // Find out the dimensions of the paragraph at given char offset.
let mut layout = paragraph.layout(area); let mut layout = paragraph.layout(area);
layout.continues_from_prev_page = self.chr > 0; 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 (used, remaining_area) = area.split_top(fit.height());
let layout = TextLayoutProxy::new(self, used); let layout = TextLayoutProxy::new(self, used);
@ -447,9 +436,9 @@ impl PageOffset {
) )
} }
fn should_place_pair_on_next_page<S: StringType>( fn should_place_pair_on_next_page(
this_paragraph: &Paragraph<S>, this_paragraph: &Paragraph<'_>,
next_paragraph: &Paragraph<S>, next_paragraph: &Paragraph<'_>,
area: Rect, area: Rect,
full_height: i16, full_height: i16,
) -> bool { ) -> bool {
@ -461,13 +450,11 @@ impl PageOffset {
let full_area = area.with_height(full_height); let full_area = area.with_height(full_height);
let key_height = this_paragraph let key_height = this_paragraph
.layout(full_area) .content()
.fit_text(this_paragraph.content.as_ref()) .map(|t| this_paragraph.layout(full_area).fit_text(t).height());
.height();
let val_height = next_paragraph let val_height = next_paragraph
.layout(full_area) .content()
.fit_text(next_paragraph.content.as_ref()) .map(|t| next_paragraph.layout(full_area).fit_text(t).height());
.height();
let screen_full_threshold = this_paragraph.style.text_font.line_height() let screen_full_threshold = this_paragraph.style.text_font.line_height()
+ next_paragraph.style.text_font.line_height(); + next_paragraph.style.text_font.line_height();
@ -497,10 +484,10 @@ struct PageBreakIterator<'a, T> {
current: Option<PageOffset>, current: Option<PageOffset>,
} }
impl<T: ParagraphSource> PageBreakIterator<'_, T> { impl<'a, T: ParagraphSource<'a>> PageBreakIterator<'_, T> {
fn dyn_next<S: StringType>( fn dyn_next(
mut area: Rect, mut area: Rect,
paragraphs: &dyn ParagraphSource<StrType = S>, paragraphs: &dyn ParagraphSource<'_>,
mut offset: PageOffset, mut offset: PageOffset,
) -> Option<PageOffset> { ) -> Option<PageOffset> {
let full_height = area.height(); let full_height = area.height();
@ -527,7 +514,7 @@ impl<T: ParagraphSource> PageBreakIterator<'_, T> {
/// Yields indices to beginnings of successive pages. First value is always /// Yields indices to beginnings of successive pages. First value is always
/// `PageOffset { 0, 0 }` even if the paragraph vector is empty. /// `PageOffset { 0, 0 }` even if the paragraph vector is empty.
impl<T: ParagraphSource> Iterator for PageBreakIterator<'_, T> { impl<'a, T: ParagraphSource<'a>> Iterator for PageBreakIterator<'_, T> {
/// `PageOffset` denotes the first paragraph that is rendered and a /// `PageOffset` denotes the first paragraph that is rendered and a
/// character offset in that paragraph. /// character offset in that paragraph.
type Item = PageOffset; type Item = PageOffset;
@ -608,9 +595,9 @@ impl<T> Checklist<T> {
} }
} }
impl<T> Component for Checklist<T> impl<'a, T> Component for Checklist<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
type Msg = Never; type Msg = Never;
@ -652,9 +639,9 @@ where
} }
} }
impl<T> Paginate for Checklist<T> impl<'a, T> Paginate for Checklist<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
fn page_count(&mut self) -> usize { fn page_count(&mut self) -> usize {
1 1
@ -664,7 +651,7 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T: ParagraphSource> crate::trace::Trace for Checklist<T> { impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Checklist<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Checklist"); t.component("Checklist");
t.int("current", self.current as i64); t.int("current", self.current as i64);
@ -672,16 +659,13 @@ impl<T: ParagraphSource> crate::trace::Trace for Checklist<T> {
} }
} }
pub trait VecExt<T> { pub trait VecExt<'a> {
fn add(&mut self, paragraph: Paragraph<T>) -> &mut Self; fn add(&mut self, paragraph: Paragraph<'a>) -> &mut Self;
} }
impl<T, const N: usize> VecExt<T> for Vec<Paragraph<T>, N> impl<'a, const N: usize> VecExt<'a> for Vec<Paragraph<'a>, N> {
where fn add(&mut self, paragraph: Paragraph<'a>) -> &mut Self {
T: AsRef<str>, if paragraph.content().is_empty() {
{
fn add(&mut self, paragraph: Paragraph<T>) -> &mut Self {
if paragraph.content.as_ref().is_empty() {
return self; return self;
} }
if self.push(paragraph).is_err() { if self.push(paragraph).is_err() {
@ -692,12 +676,10 @@ where
} }
} }
impl<T: StringType, const N: usize> ParagraphSource for Vec<Paragraph<T>, N> { impl<'a, const N: usize> ParagraphSource<'a> for Vec<Paragraph<'a>, N> {
type StrType = T; fn at(&self, index: usize, offset: usize) -> Paragraph<'a> {
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
let para = &self[index]; let para = &self[index];
para.map(|content| content.skip_prefix(offset)) para.skip_prefix(offset)
} }
fn size(&self) -> usize { fn size(&self) -> usize {
@ -705,12 +687,10 @@ impl<T: StringType, const N: usize> ParagraphSource for Vec<Paragraph<T>, N> {
} }
} }
impl<T: StringType, const N: usize> ParagraphSource for [Paragraph<T>; N] { impl<'a, const N: usize> ParagraphSource<'a> for [Paragraph<'a>; N] {
type StrType = T; fn at(&self, index: usize, offset: usize) -> Paragraph<'a> {
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
let para = &self[index]; let para = &self[index];
para.map(|content| content.skip_prefix(offset)) para.skip_prefix(offset)
} }
fn size(&self) -> usize { fn size(&self) -> usize {
@ -718,12 +698,10 @@ impl<T: StringType, const N: usize> ParagraphSource for [Paragraph<T>; N] {
} }
} }
impl<T: StringType> ParagraphSource for Paragraph<T> { impl<'a> ParagraphSource<'a> for Paragraph<'a> {
type StrType = T; fn at(&self, index: usize, offset: usize) -> Paragraph<'a> {
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
assert_eq!(index, 0); assert_eq!(index, 0);
self.map(|content| content.skip_prefix(offset)) self.skip_prefix(offset)
} }
fn size(&self) -> usize { fn size(&self) -> usize {

View File

@ -389,7 +389,7 @@ extern "C" fn ui_layout_progress_event(n_args: usize, args: *const Obj) -> Obj {
let this: Gc<LayoutObj> = args[0].try_into()?; let this: Gc<LayoutObj> = args[0].try_into()?;
let value: u16 = args[1].try_into()?; let value: u16 = args[1].try_into()?;
let description: StrBuffer = args[2].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) Ok(msg)
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }

View File

@ -8,7 +8,6 @@ use crate::{
util::{iter_into_array, try_or_raise}, util::{iter_into_array, try_or_raise},
}, },
storage::{get_avatar_len, load_avatar}, storage::{get_avatar_len, load_avatar},
strutil::SkipPrefix,
ui::{ ui::{
component::text::{ component::text::{
paragraphs::{Paragraph, ParagraphSource}, paragraphs::{Paragraph, ParagraphSource},
@ -62,10 +61,8 @@ pub struct ConfirmBlob {
pub data_font: &'static TextStyle, pub data_font: &'static TextStyle,
} }
impl ParagraphSource for ConfirmBlob { impl ParagraphSource<'static> for ConfirmBlob {
type StrType = StrBuffer; fn at(&self, index: usize, offset: usize) -> Paragraph<'static> {
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
match index { match index {
0 => Paragraph::new(self.description_font, self.description.skip_prefix(offset)), 0 => Paragraph::new(self.description_font, self.description.skip_prefix(offset)),
1 => Paragraph::new(self.extra_font, self.extra.skip_prefix(offset)), 1 => Paragraph::new(self.extra_font, self.extra.skip_prefix(offset)),
@ -102,10 +99,8 @@ impl PropsList {
} }
} }
impl ParagraphSource for PropsList { impl ParagraphSource<'static> for PropsList {
type StrType = StrBuffer; fn at(&self, index: usize, offset: usize) -> Paragraph<'static> {
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
let block = move || { let block = move || {
let entry = self.items.get(index / 2)?; let entry = self.items.get(index / 2)?;
let [key, value, value_is_mono]: [Obj; 3] = iter_into_array(entry)?; let [key, value, value_is_mono]: [Obj; 3] = iter_into_array(entry)?;

View File

@ -22,8 +22,8 @@ const QR_BORDER: i16 = 3;
pub struct AddressDetails { pub struct AddressDetails {
qr_code: Qr, qr_code: Qr,
details_view: Paragraphs<ParagraphVecShort<StrBuffer>>, details_view: Paragraphs<ParagraphVecShort<'static>>,
xpub_view: Frame<Paragraphs<Paragraph<StrBuffer>>, StrBuffer>, xpub_view: Frame<Paragraphs<Paragraph<'static>>, StrBuffer>,
xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>, xpubs: Vec<(StrBuffer, StrBuffer), MAX_XPUBS>,
current_page: usize, current_page: usize,
current_subpage: usize, current_subpage: usize,
@ -43,16 +43,13 @@ impl AddressDetails {
let details_view = { let details_view = {
let mut para = ParagraphVecShort::new(); let mut para = ParagraphVecShort::new();
if let Some(account) = account { if let Some(account) = account {
para.add(Paragraph::new( para.add(Paragraph::new(&theme::TEXT_BOLD, TR::words__account_colon));
&theme::TEXT_BOLD,
TR::words__account_colon.try_into()?,
));
para.add(Paragraph::new(&theme::TEXT_MONO, account)); para.add(Paragraph::new(&theme::TEXT_MONO, account));
} }
if let Some(path) = path { if let Some(path) = path {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_BOLD, &theme::TEXT_BOLD,
TR::address_details__derivation_path.try_into()?, TR::address_details__derivation_path,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, path)); para.add(Paragraph::new(&theme::TEXT_MONO, path));
} }
@ -60,7 +57,7 @@ impl AddressDetails {
}; };
let xpub_view = Frame::new( let xpub_view = Frame::new(
"".into(), "".into(),
Paragraph::new(&theme::TEXT_MONO_DATA, "".into()).into_paragraphs(), Paragraph::new(&theme::TEXT_MONO_DATA, "").into_paragraphs(),
); );
let result = Self { let result = Self {

View File

@ -78,7 +78,7 @@ pub struct Page<T>
where where
T: StringType + Clone, T: StringType + Clone,
{ {
formatted: FormattedText<T>, formatted: FormattedText,
btn_layout: ButtonLayout, btn_layout: ButtonLayout,
btn_actions: ButtonActions, btn_actions: ButtonActions,
current_page: usize, current_page: usize,
@ -95,7 +95,7 @@ where
pub fn new( pub fn new(
btn_layout: ButtonLayout, btn_layout: ButtonLayout,
btn_actions: ButtonActions, btn_actions: ButtonActions,
formatted: FormattedText<T>, formatted: FormattedText,
) -> Self { ) -> Self {
let mut page = Self { let mut page = Self {
formatted, formatted,

View File

@ -274,7 +274,7 @@ where
pub fn start(&mut self, ctx: &mut EventCtx) { pub fn start(&mut self, ctx: &mut EventCtx) {
self.start_time = Some(Instant::now()); 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| { self.loader.mutate(ctx, |ctx, loader| {
loader.request_paint(ctx); loader.request_paint(ctx);
}); });
@ -318,7 +318,7 @@ where
let percentage = self.percentage(now); let percentage = self.percentage(now);
let new_loader_value = (percentage * LOADER_MAX as u32) / 100; let new_loader_value = (percentage * LOADER_MAX as u32) / 100;
self.loader 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 // Returning only after the loader was fully painted
if percentage >= 100 { if percentage >= 100 {
return Some(LoaderMsg::GrownCompletely); return Some(LoaderMsg::GrownCompletely);

View File

@ -1,7 +1,6 @@
use core::mem; use core::mem;
use crate::{ use crate::{
error::Error,
strutil::StringType, strutil::StringType,
ui::{ ui::{
component::{ component::{
@ -22,9 +21,6 @@ const BOTTOM_DESCRIPTION_MARGIN: i16 = 10;
const LOADER_Y_OFFSET_TITLE: i16 = -10; const LOADER_Y_OFFSET_TITLE: i16 = -10;
const LOADER_Y_OFFSET_NO_TITLE: i16 = -20; const LOADER_Y_OFFSET_NO_TITLE: i16 = -20;
// Clippy was complaining about `very complex type used`
type UpdateDescriptionFn<T, Error> = fn(&str) -> Result<T, Error>;
pub struct Progress<T> pub struct Progress<T>
where where
T: StringType, T: StringType,
@ -33,9 +29,8 @@ where
value: u16, value: u16,
loader_y_offset: i16, loader_y_offset: i16,
indeterminate: bool, indeterminate: bool,
description: Child<Paragraphs<Paragraph<T>>>, description: Child<Paragraphs<Paragraph<'static>>>,
description_pad: Pad, description_pad: Pad,
update_description: Option<UpdateDescriptionFn<T, Error>>,
icon: Icon, icon: Icon,
} }
@ -55,7 +50,6 @@ where
Paragraph::new(&theme::TEXT_NORMAL, description).centered(), Paragraph::new(&theme::TEXT_NORMAL, description).centered(),
)), )),
description_pad: Pad::with_background(theme::BG), description_pad: Pad::with_background(theme::BG),
update_description: None,
icon: theme::ICON_TICK_FAT, icon: theme::ICON_TICK_FAT,
} }
} }
@ -65,14 +59,6 @@ where
self self
} }
pub fn with_update_description(
mut self,
update_description: UpdateDescriptionFn<T, Error>,
) -> Self {
self.update_description = Some(update_description);
self
}
pub fn with_icon(mut self, icon: Icon) -> Self { pub fn with_icon(mut self, icon: Icon) -> Self {
self.icon = icon; self.icon = icon;
self self
@ -105,10 +91,7 @@ where
.inner() .inner()
.inner() .inner()
.content() .content()
.as_ref() .map(|t| t.chars().filter(|c| *c == '\n').count() as i16);
.chars()
.filter(|c| *c == '\n')
.count() as i16;
let no_title_case = (Rect::zero(), Self::AREA, LOADER_Y_OFFSET_NO_TITLE); 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 { 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 { if mem::replace(&mut self.value, new_value) != new_value {
self.request_paint(ctx); self.request_paint(ctx);
} }
if let Some(update_description) = self.update_description { self.description.mutate(ctx, |ctx, para| {
self.description.mutate(ctx, |ctx, para| { // NOTE: not doing any change for empty new descriptions
// NOTE: not doing any change for empty new descriptions // (currently, there is no use-case for deleting the description)
// (currently, there is no use-case for deleting the description) if !new_description.is_empty() && para.inner_mut().content() != &new_description {
if !new_description.is_empty() para.inner_mut().update(new_description);
&& para.inner_mut().content().as_ref() != new_description para.change_page(0); // Recompute bounding box.
{ ctx.request_paint();
let new_description = unwrap!((update_description)(new_description)); self.description_pad.clear();
para.inner_mut().update(new_description); }
para.change_page(0); // Recompute bounding box. });
ctx.request_paint();
self.description_pad.clear();
}
});
}
} }
None None
} }

View File

@ -77,9 +77,9 @@ where
} }
} }
impl<T> ComponentMsgObj for Paragraphs<T> impl<'a, T> ComponentMsgObj for Paragraphs<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!() 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 title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL) let ops = OpTextLayout::new(theme::TEXT_NORMAL)
.text_normal(TR::reset__by_continuing.try_into()?) .text_normal(TR::reset__by_continuing)
.next_page() .next_page()
.text_normal(TR::reset__more_info_at.try_into()?) .text_normal(TR::reset__more_info_at)
.newline() .newline()
.text_bold(TR::reset__tos_link.try_into()?); .text_bold(TR::reset__tos_link);
let formatted = FormattedText::new(ops).vertically_centered(); let formatted = FormattedText::new(ops).vertically_centered();
content_in_button_page(title, formatted, button, Some("".into()), false) 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| { let block = move |_args: &[Obj], _kwargs: &Map| {
// cached allocated translations that get_page can reuse // cached allocated translations that get_page can reuse
let tr_title_success: StrBuffer = TR::words__title_success.try_into()?; 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_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 { let get_page = move |page_index| match page_index {
0 => { 0 => {
let btn_layout = ButtonLayout::text_none_arrow_wide(TR::buttons__skip.into()); let btn_layout = ButtonLayout::text_none_arrow_wide(TR::buttons__skip.into());
let btn_actions = ButtonActions::cancel_none_next(); let btn_actions = ButtonActions::cancel_none_next();
let ops = OpTextLayout::new(theme::TEXT_NORMAL) let ops = OpTextLayout::new(theme::TEXT_NORMAL)
.text_normal(tr_new_wallet_created) .text_normal(TR::backup__new_wallet_created)
.newline() .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(); 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_title_success)
} }
1 => { 1 => {
let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__back_up.into()); let btn_layout = ButtonLayout::up_arrow_none_text(TR::buttons__back_up.into());
let btn_actions = ButtonActions::prev_none_confirm(); 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(); let formatted = FormattedText::new(ops).vertically_centered();
Page::<StrBuffer>::new(btn_layout, btn_actions, formatted) Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
.with_title(tr_title_backup_wallet) .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 total_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_amount)?.try_into()?;
let paragraphs = Paragraphs::new([ let paragraphs = Paragraphs::new([
Paragraph::new( Paragraph::new(&theme::TEXT_BOLD, TR::joint__you_are_contributing),
&theme::TEXT_BOLD,
TR::joint__you_are_contributing.try_into()?,
),
Paragraph::new(&theme::TEXT_MONO, spending_amount), Paragraph::new(&theme::TEXT_MONO, spending_amount),
Paragraph::new( Paragraph::new(&theme::TEXT_BOLD, TR::joint__to_the_total_amount),
&theme::TEXT_BOLD,
TR::joint__to_the_total_amount.try_into()?,
),
Paragraph::new(&theme::TEXT_MONO, 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([ 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_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), 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 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()?; 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| { let get_page = move |page_index| {
match page_index { match page_index {
0 => { 0 => {
@ -701,7 +686,7 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma
.text_mono(fee_amount); .text_mono(fee_amount);
let formatted = FormattedText::new(ops); let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted) Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
} }
1 => { 1 => {
// Fee rate info // 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 fee_rate_amount = fee_rate_amount.unwrap_or_default();
let ops = OpTextLayout::new(theme::TEXT_MONO) let ops = OpTextLayout::new(theme::TEXT_MONO)
.text_bold(tr_title_fee) .text_bold(TR::confirm_total__title_fee)
.newline() .newline()
.newline() .newline()
.newline_half() .newline_half()
.text_bold(tr_fee_rate) .text_bold(TR::confirm_total__fee_rate)
.newline() .newline()
.text_mono(fee_rate_amount); .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 // TODO: include wallet info when available
let ops = OpTextLayout::new(theme::TEXT_MONO) let ops = OpTextLayout::new(theme::TEXT_MONO)
.text_bold(tr_title_sending_from) .text_bold(TR::confirm_total__title_sending_from)
.newline() .newline()
.newline() .newline()
.newline_half() .newline_half()
.text_bold(tr_account) .text_bold(TR::words__account_colon)
.newline() .newline()
.text_mono(account_label); .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.next_page();
} }
ops = ops ops = ops
.text_bold(unwrap!(key.try_into())) .text_bold(unwrap!(TString::try_from(key)))
.newline() .newline()
.text_mono(unwrap!(value.try_into())); .text_mono(unwrap!(TString::try_from(value)));
} }
let formatted = FormattedText::new(ops).vertically_centered(); 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) /// (title, text, btn_layout, btn_actions, text_y_offset)
fn tutorial_screen( fn tutorial_screen(
title: StrBuffer, title: StrBuffer,
text: StrBuffer, text: TR,
btn_layout: ButtonLayout, btn_layout: ButtonLayout,
btn_actions: ButtonActions, btn_actions: ButtonActions,
) -> Page<StrBuffer> { ) -> Page<StrBuffer> {
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL).text_normal(text); let ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_centered(); 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)
} }
@ -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 // cached allocated translated strings that get_page can reuse
let tr_title_hello: StrBuffer = TR::tutorial__title_hello.try_into()?; 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_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_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_confirm: StrBuffer = TR::buttons__confirm.try_into()?;
let tr_middle_click: StrBuffer = TR::tutorial__middle_click.try_into()?;
let tr_title_tutorial_complete: StrBuffer = let tr_title_tutorial_complete: StrBuffer =
TR::tutorial__title_tutorial_complete.try_into()?; 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_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| { let get_page = move |page_index| {
// Lazy-loaded list of screens to show, with custom content, // 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 // title, text, btn_layout, btn_actions
0 => tutorial_screen( 0 => tutorial_screen(
tr_title_hello, tr_title_hello,
tr_welcome_press_right, TR::tutorial__welcome_press_right,
ButtonLayout::cancel_none_arrow(), ButtonLayout::cancel_none_arrow(),
ButtonActions::last_none_next(), ButtonActions::last_none_next(),
), ),
1 => tutorial_screen( 1 => tutorial_screen(
"".into(), "".into(),
tr_use_trezor, TR::tutorial__use_trezor,
ButtonLayout::arrow_none_arrow(), ButtonLayout::arrow_none_arrow(),
ButtonActions::prev_none_next(), ButtonActions::prev_none_next(),
), ),
2 => tutorial_screen( 2 => tutorial_screen(
tr_hold_to_confirm, tr_hold_to_confirm,
tr_press_and_hold, TR::tutorial__press_and_hold,
ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()), ButtonLayout::arrow_none_htc(TR::buttons__hold_to_confirm.into()),
ButtonActions::prev_none_next(), ButtonActions::prev_none_next(),
), ),
3 => tutorial_screen( 3 => tutorial_screen(
tr_title_screen_scroll, tr_title_screen_scroll,
tr_scroll_down, TR::tutorial__scroll_down,
ButtonLayout::arrow_none_text(TR::buttons__continue.into()), ButtonLayout::arrow_none_text(TR::buttons__continue.into()),
ButtonActions::prev_none_next(), ButtonActions::prev_none_next(),
), ),
4 => tutorial_screen( 4 => tutorial_screen(
tr_confirm, tr_confirm,
tr_middle_click, TR::tutorial__middle_click,
ButtonLayout::none_armed_none(TR::buttons__confirm.into()), ButtonLayout::none_armed_none(TR::buttons__confirm.into()),
ButtonActions::none_next_none(), ButtonActions::none_next_none(),
), ),
5 => tutorial_screen( 5 => tutorial_screen(
tr_title_tutorial_complete, tr_title_tutorial_complete,
tr_ready_to_use, TR::tutorial__ready_to_use,
ButtonLayout::text_none_text( ButtonLayout::text_none_text(
TR::buttons__again.into(), TR::buttons__again.into(),
TR::buttons__continue.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( 6 => tutorial_screen(
tr_title_skip, tr_title_skip,
tr_sure_you_want_skip, TR::tutorial__sure_you_want_skip,
ButtonLayout::arrow_none_text(TR::buttons__skip.into()), ButtonLayout::arrow_none_text(TR::buttons__skip.into()),
ButtonActions::beginning_none_cancel(), 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(); let mut paragraphs_vec = ParagraphVecShort::new();
paragraphs_vec 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_MONO, change))
.add( .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__transaction_fee).no_break())
Paragraph::new(
&theme::TEXT_BOLD,
TR::modify_fee__transaction_fee.try_into()?,
)
.no_break(),
)
.add(Paragraph::new(&theme::TEXT_MONO, total_fee_new)); .add(Paragraph::new(&theme::TEXT_MONO, total_fee_new));
if let Some(fee_rate_amount) = fee_rate_amount { if let Some(fee_rate_amount) = fee_rate_amount {
paragraphs_vec paragraphs_vec
.add( .add(Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__fee_rate).no_break())
Paragraph::new(&theme::TEXT_BOLD, TR::modify_fee__fee_rate.try_into()?)
.no_break(),
)
.add(Paragraph::new(&theme::TEXT_MONO, fee_rate_amount)); .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 { 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 block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; 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<List> = kwargs.get(Qstr::MP_QSTR_items)?.try_into()?; let items: Gc<List> = kwargs.get(Qstr::MP_QSTR_items)?.try_into()?;
// Cache the page count so that we can move `items` into the closure. // 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. // the need of any allocation here in Rust.
let get_page = move |page_index| { let get_page = move |page_index| {
let item_obj = unwrap!(items.get(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 { let (btn_layout, btn_actions) = if page_count == 1 {
// There is only one page // 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 ops = OpTextLayout::new(theme::TEXT_NORMAL).text_normal(text);
let formatted = FormattedText::new(ops).vertically_centered(); let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted) Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
}; };
let pages = FlowPages::new(get_page, page_count); 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. // the need of any allocation here in Rust.
let get_page = move |page_index| { let get_page = move |page_index| {
let account_obj = unwrap!(accounts.get(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 { let (btn_layout, btn_actions) = if page_count == 1 {
// There is only one page // 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); .text_bold(account);
let formatted = FormattedText::new(ops); let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted) Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
}; };
let pages = FlowPages::new(get_page, page_count); 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_layout = ButtonLayout::none_armed_none(button);
let btn_actions = ButtonActions::none_confirm_none(); let btn_actions = ButtonActions::none_confirm_none();
let mut ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL); let mut ops = OpTextLayout::new(theme::TEXT_NORMAL);
ops = ops.alignment(geometry::Alignment::Center); ops = ops.alignment(geometry::Alignment::Center);
if !warning.is_empty() { if !warning.is_empty() {
ops = ops.text_bold(warning).newline(); 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); ops = ops.text_normal(description);
} }
let formatted = FormattedText::new(ops).vertically_centered(); let formatted = FormattedText::new(ops).vertically_centered();
Page::new(btn_layout, btn_actions, formatted) Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
}; };
let pages = FlowPages::new(get_page, 1); let pages = FlowPages::new(get_page, 1);
let obj = LayoutObj::new(Flow::new(pages))?; 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 block = move |_args: &[Obj], kwargs: &Map| {
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; 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| { let get_page = move |page_index| {
assert!(page_index == 0); assert!(page_index == 0);
let btn_layout = ButtonLayout::arrow_none_text(TR::buttons__quit.into()); let btn_layout = ButtonLayout::arrow_none_text(TR::buttons__quit.into());
let btn_actions = ButtonActions::cancel_none_confirm(); let btn_actions = ButtonActions::cancel_none_confirm();
let ops = OpTextLayout::<StrBuffer>::new(theme::TEXT_NORMAL) let ops = OpTextLayout::new(theme::TEXT_NORMAL)
.text_bold(title) .text_bold(title)
.newline() .newline()
.newline_half() .newline_half()
.text_normal(tr_contact_support_at) .text_normal(TR::addr_mismatch__contact_support_at)
.newline() .newline()
.text_bold(tr_support_url); .text_bold(TR::addr_mismatch__support_url);
let formatted = FormattedText::new(ops); let formatted = FormattedText::new(ops);
Page::new(btn_layout, btn_actions, formatted) Page::<StrBuffer>::new(btn_layout, btn_actions, formatted)
}; };
let pages = FlowPages::new(get_page, 1); 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( let obj = LayoutObj::new(Frame::new(
title, title,
ShowMore::<Paragraphs<ParagraphVecShort<StrBuffer>>>::new( ShowMore::<Paragraphs<ParagraphVecShort>>::new(
paragraphs.into_paragraphs(), paragraphs.into_paragraphs(),
verb_cancel, verb_cancel,
button, 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 // Decreasing bottom padding between paragraphs to fit one screen
let paragraphs = Paragraphs::new([ let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_rounds.try_into()?) Paragraph::new(&theme::TEXT_BOLD, TR::coinjoin__max_rounds).with_bottom_padding(2),
.with_bottom_padding(2),
Paragraph::new(&theme::TEXT_MONO, max_rounds), 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) .with_bottom_padding(2)
.no_break(), .no_break(),
Paragraph::new(&theme::TEXT_MONO, max_feerate).with_bottom_padding(2), 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 paragraphs
.add(Paragraph::new( .add(Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
TR::recovery__only_first_n_letters.try_into()?, TR::recovery__only_first_n_letters,
)) ))
.add(Paragraph::new( .add(Paragraph::new(
&theme::TEXT_NORMAL, &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) .and_then(Obj::try_into_option)
.unwrap_or(None); .unwrap_or(None);
let mut progress = let mut progress = Progress::new(indeterminate, description);
Progress::new(indeterminate, description).with_update_description(StrBuffer::alloc);
if let Some(title) = title { if let Some(title) = title {
progress = progress.with_title(title); progress = progress.with_title(title);
}; };

View File

@ -20,8 +20,8 @@ const MAX_XPUBS: usize = 16;
pub struct AddressDetails<T> { pub struct AddressDetails<T> {
qr_code: Frame<Qr, T>, qr_code: Frame<Qr, T>,
details: Frame<Paragraphs<ParagraphVecShort<StrBuffer>>, T>, details: Frame<Paragraphs<ParagraphVecShort<'static>>, T>,
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>, xpub_view: Frame<Paragraphs<Paragraph<'static>>, T>,
xpubs: Vec<(T, T), MAX_XPUBS>, xpubs: Vec<(T, T), MAX_XPUBS>,
xpub_page_count: Vec<u8, MAX_XPUBS>, xpub_page_count: Vec<u8, MAX_XPUBS>,
current_page: usize, current_page: usize,
@ -46,14 +46,14 @@ where
if let Some(a) = account { if let Some(a) = account {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
TR::words__account_colon.try_into()?, TR::words__account_colon,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, a)); para.add(Paragraph::new(&theme::TEXT_MONO, a));
} }
if let Some(p) = path { if let Some(p) = path {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
TR::address_details__derivation_path.try_into()?, TR::address_details__derivation_path,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, p)); para.add(Paragraph::new(&theme::TEXT_MONO, p));
} }
@ -75,7 +75,7 @@ where
xpub_view: Frame::left_aligned( xpub_view: Frame::left_aligned(
theme::label_title(), theme::label_title(),
" \n ".into(), " \n ".into(),
Paragraph::new(&theme::TEXT_MONO, "".into()).into_paragraphs(), Paragraph::new(&theme::TEXT_MONO, "").into_paragraphs(),
) )
.with_cancel_button() .with_cancel_button()
.with_border(theme::borders_horizontal_scroll()), .with_border(theme::borders_horizontal_scroll()),

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
strutil::StringType, strutil::TString,
ui::{ ui::{
component::{ component::{
image::BlendedImage, image::BlendedImage,
@ -91,18 +91,17 @@ where
} }
} }
pub struct IconDialog<T, U> { pub struct IconDialog<U> {
image: Child<BlendedImage>, image: Child<BlendedImage>,
paragraphs: Paragraphs<ParagraphVecShort<T>>, paragraphs: Paragraphs<ParagraphVecShort<'static>>,
controls: Child<U>, controls: Child<U>,
} }
impl<T, U> IconDialog<T, U> impl<U> IconDialog<U>
where where
T: StringType,
U: Component, U: Component,
{ {
pub fn new(icon: BlendedImage, title: T, controls: U) -> Self { pub fn new(icon: BlendedImage, title: impl Into<TString<'static>>, controls: U) -> Self {
Self { Self {
image: Child::new(icon), image: Child::new(icon),
paragraphs: Paragraphs::new(ParagraphVecShort::from_iter([Paragraph::new( paragraphs: Paragraphs::new(ParagraphVecShort::from_iter([Paragraph::new(
@ -119,26 +118,26 @@ where
} }
} }
pub fn with_paragraph(mut self, para: Paragraph<T>) -> Self { pub fn with_paragraph(mut self, para: Paragraph<'static>) -> Self {
if !para.content().as_ref().is_empty() { if !para.content().is_empty() {
self.paragraphs.inner_mut().add(para); self.paragraphs.inner_mut().add(para);
} }
self self
} }
pub fn with_text(self, style: &'static TextStyle, text: T) -> Self { pub fn with_text(self, style: &'static TextStyle, text: impl Into<TString<'static>>) -> Self {
self.with_paragraph(Paragraph::new(style, text).centered()) self.with_paragraph(Paragraph::new(style, text).centered())
} }
pub fn with_description(self, description: T) -> Self { pub fn with_description(self, description: impl Into<TString<'static>>) -> Self {
self.with_text(&theme::TEXT_NORMAL_OFF_WHITE, description) 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<TString<'static>>) -> Self {
self.with_text(&theme::TEXT_MONO, value) self.with_text(&theme::TEXT_MONO, value)
} }
pub fn new_shares(lines: [T; 4], controls: U) -> Self { pub fn new_shares(lines: [impl Into<TString<'static>>; 4], controls: U) -> Self {
let [l0, l1, l2, l3] = lines; let [l0, l1, l2, l3] = lines;
Self { Self {
image: Child::new(BlendedImage::new( image: Child::new(BlendedImage::new(
@ -165,9 +164,8 @@ where
pub const VALUE_SPACE: i16 = 5; pub const VALUE_SPACE: i16 = 5;
} }
impl<T, U> Component for IconDialog<T, U> impl<U> Component for IconDialog<U>
where where
T: StringType,
U: Component, U: Component,
{ {
type Msg = DialogMsg<Never, U::Msg>; type Msg = DialogMsg<Never, U::Msg>;
@ -207,9 +205,8 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for IconDialog<T, U> impl<U> crate::trace::Trace for IconDialog<U>
where where
T: StringType,
U: crate::trace::Trace, U: crate::trace::Trace,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -29,7 +29,7 @@ where
area: Rect, area: Rect,
description_func: F, description_func: F,
input: Child<NumberInput>, input: Child<NumberInput>,
paragraphs: Child<Paragraphs<Paragraph<T>>>, paragraphs: Child<Paragraphs<Paragraph<'static>>>,
paragraphs_pad: Pad, paragraphs_pad: Pad,
info_button: Child<Button<StrBuffer>>, info_button: Child<Button<StrBuffer>>,
confirm_button: Child<Button<StrBuffer>>, confirm_button: Child<Button<StrBuffer>>,

View File

@ -485,16 +485,13 @@ impl PageLayout {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde_json;
use crate::{ use crate::{
strutil::SkipPrefix,
trace::tests::trace, trace::tests::trace,
ui::{ ui::{
component::text::paragraphs::{Paragraph, Paragraphs}, component::text::paragraphs::{Paragraph, Paragraphs},
event::TouchEvent, event::TouchEvent,
geometry::Point, geometry::Point,
model_tt::{constant, theme}, model_tt::constant,
}, },
}; };
@ -502,12 +499,6 @@ mod tests {
const SCREEN: Rect = constant::screen().inset(theme::borders()); 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)]) { fn swipe(component: &mut impl Component, points: &[(i16, i16)]) {
let last = points.len().saturating_sub(1); let last = points.len().saturating_sub(1);
let mut first = true; let mut first = true;
@ -538,7 +529,7 @@ mod tests {
#[test] #[test]
fn paragraphs_empty() { fn paragraphs_empty() {
let mut page = ButtonPage::<_, &'static str>::new( let mut page = ButtonPage::<_, &'static str>::new(
Paragraphs::<[Paragraph<&'static str>; 0]>::new([]), Paragraphs::<[Paragraph<'static>; 0]>::new([]),
theme::BG, theme::BG,
); );
page.place(SCREEN); page.place(SCREEN);

View File

@ -1,8 +1,7 @@
use core::mem; use core::mem;
use crate::{ use crate::{
error::Error, strutil::{StringType, TString},
strutil::StringType,
ui::{ ui::{
component::{ component::{
base::ComponentExt, base::ComponentExt,
@ -24,9 +23,8 @@ pub struct Progress<T> {
value: u16, value: u16,
loader_y_offset: i16, loader_y_offset: i16,
indeterminate: bool, indeterminate: bool,
description: Child<Paragraphs<Paragraph<T>>>, description: Child<Paragraphs<Paragraph<'static>>>,
description_pad: Pad, description_pad: Pad,
update_description: fn(&str) -> Result<T, Error>,
} }
impl<T> Progress<T> impl<T> Progress<T>
@ -35,12 +33,7 @@ where
{ {
const AREA: Rect = constant::screen().inset(theme::borders()); const AREA: Rect = constant::screen().inset(theme::borders());
pub fn new( pub fn new(title: T, indeterminate: bool, description: TString<'static>) -> Self {
title: T,
indeterminate: bool,
description: T,
update_description: fn(&str) -> Result<T, Error>,
) -> Self {
Self { Self {
title: Label::centered(title, theme::label_progress()).into_child(), title: Label::centered(title, theme::label_progress()).into_child(),
value: 0, value: 0,
@ -51,7 +44,6 @@ where
) )
.into_child(), .into_child(),
description_pad: Pad::with_background(theme::BG), description_pad: Pad::with_background(theme::BG),
update_description,
} }
} }
} }
@ -68,10 +60,7 @@ where
.inner() .inner()
.inner() .inner()
.content() .content()
.as_ref() .map(|t| t.chars().filter(|c| *c == '\n').count() as i16);
.chars()
.filter(|c| *c == '\n')
.count() as i16;
let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y); let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y);
let (loader, description) = let (loader, description) =
rest.split_bottom(Font::NORMAL.line_height() * description_lines); rest.split_bottom(Font::NORMAL.line_height() * description_lines);
@ -90,8 +79,7 @@ where
ctx.request_paint(); ctx.request_paint();
} }
self.description.mutate(ctx, |ctx, para| { self.description.mutate(ctx, |ctx, para| {
if para.inner_mut().content().as_ref() != new_description { if para.inner_mut().content() != &new_description {
let new_description = unwrap!((self.update_description)(new_description));
para.inner_mut().update(new_description); para.inner_mut().update(new_description);
para.change_page(0); // Recompute bounding box. para.change_page(0); // Recompute bounding box.
ctx.request_paint(); ctx.request_paint();

View File

@ -128,9 +128,8 @@ where
} }
} }
impl<T, U> ComponentMsgObj for IconDialog<T, U> impl<U> ComponentMsgObj for IconDialog<U>
where where
T: StringType,
U: Component, U: Component,
<U as Component>::Msg: TryInto<Obj, Error = Error>, <U as Component>::Msg: TryInto<Obj, Error = Error>,
{ {
@ -223,27 +222,24 @@ where
// Clippy/compiler complains about conflicting implementations // Clippy/compiler complains about conflicting implementations
// TODO move the common impls to a common module // TODO move the common impls to a common module
#[cfg(not(feature = "clippy"))] #[cfg(not(feature = "clippy"))]
impl<T> ComponentMsgObj for Paragraphs<T> impl<'a, T> ComponentMsgObj for Paragraphs<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!() unreachable!()
} }
} }
impl<T> ComponentMsgObj for FormattedText<T> impl ComponentMsgObj for FormattedText {
where
T: StringType + Clone,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!() unreachable!()
} }
} }
impl<T> ComponentMsgObj for Checklist<T> impl<'a, T> ComponentMsgObj for Checklist<T>
where where
T: ParagraphSource, T: ParagraphSource<'a>,
{ {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!() unreachable!()
@ -298,10 +294,9 @@ impl ComponentMsgObj for Lockscreen {
} }
} }
impl<T, S> ComponentMsgObj for (GridPlaced<Paragraphs<T>>, GridPlaced<FormattedText<S>>) impl<'a, T> ComponentMsgObj for (GridPlaced<Paragraphs<T>>, GridPlaced<FormattedText>)
where where
T: ParagraphSource, T: ParagraphSource<'a>,
S: StringType + Clone,
{ {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!() 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); let mut ops = OpTextLayout::new(theme::TEXT_NORMAL);
for item in IterBuf::new().try_iterate(items)? { for item in IterBuf::new().try_iterate(items)? {
if item.is_str() { if item.is_str() {
ops = ops.text_normal(item.try_into()?) ops = ops.text_normal(StrBuffer::try_from(item)?)
} else { } else {
let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?; let [emphasis, text]: [Obj; 2] = util::iter_into_array(item)?;
let text: StrBuffer = text.try_into()?; 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 title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
let par_array: [Paragraph<StrBuffer>; 3] = [ let par_array: [Paragraph<'static>; 3] = [
Paragraph::new(&theme::TEXT_NORMAL, TR::reset__by_continuing.try_into()?) Paragraph::new(&theme::TEXT_NORMAL, TR::reset__by_continuing).with_bottom_padding(17), /* simulating a carriage return */
.with_bottom_padding(17), // simulating a carriage return Paragraph::new(&theme::TEXT_NORMAL, TR::reset__more_info_at),
Paragraph::new(&theme::TEXT_NORMAL, TR::reset__more_info_at.try_into()?), Paragraph::new(&theme::TEXT_DEMIBOLD, TR::reset__tos_link),
Paragraph::new(&theme::TEXT_DEMIBOLD, TR::reset__tos_link.try_into()?),
]; ];
let paragraphs = Paragraphs::new(par_array); let paragraphs = Paragraphs::new(par_array);
let buttons = Button::cancel_confirm( 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 amount_new: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?;
let description = if sign < 0 { let description = if sign < 0 {
TR::modify_amount__decrease_amount.try_into()? TR::modify_amount__decrease_amount
} else { } else {
TR::modify_amount__increase_amount.try_into()? TR::modify_amount__increase_amount
}; };
let paragraphs = Paragraphs::new([ let paragraphs = Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, description), Paragraph::new(&theme::TEXT_NORMAL, description),
Paragraph::new(&theme::TEXT_MONO, amount_change), Paragraph::new(&theme::TEXT_MONO, amount_change),
Paragraph::new( Paragraph::new(&theme::TEXT_NORMAL, TR::modify_amount__new_amount),
&theme::TEXT_NORMAL,
TR::modify_amount__new_amount.try_into()?,
),
Paragraph::new(&theme::TEXT_MONO, amount_new), 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 { let (description, change, total_label) = match sign {
s if s < 0 => ( s if s < 0 => (
TR::modify_fee__decrease_fee.try_into()?, TR::modify_fee__decrease_fee,
user_fee_change, user_fee_change,
TR::modify_fee__new_transaction_fee.try_into()?, TR::modify_fee__new_transaction_fee,
), ),
s if s > 0 => ( s if s > 0 => (
TR::modify_fee__increase_fee.try_into()?, TR::modify_fee__increase_fee,
user_fee_change, 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(), 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 max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?;
let paragraphs = Paragraphs::new([ 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_MONO, max_rounds),
Paragraph::new( Paragraph::new(&theme::TEXT_NORMAL, TR::coinjoin__max_mining_fee),
&theme::TEXT_NORMAL,
TR::coinjoin__max_mining_fee.try_into()?,
),
Paragraph::new(&theme::TEXT_MONO, max_feerate), 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()? TR::recovery__title.try_into()?
}; };
let paragraphs = Paragraphs::new(Paragraph::<StrBuffer>::new( let paragraphs = Paragraphs::new(Paragraph::new(
&theme::TEXT_DEMIBOLD, &theme::TEXT_DEMIBOLD,
TR::recovery__select_num_of_words.try_into()?, TR::recovery__select_num_of_words,
)); ));
let obj = LayoutObj::new(Frame::left_aligned( 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 // Description updates are received as &str and we need to provide a way to
// convert them to StrBuffer. // convert them to StrBuffer.
let obj = LayoutObj::new(Progress::new( let obj = LayoutObj::new(Progress::new(title, indeterminate, description.into()))?;
title,
indeterminate,
description,
StrBuffer::alloc,
))?;
Ok(obj.into()) Ok(obj.into())
}; };
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }