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

pull/3649/head
matejcik 1 month ago
parent c5c1321788
commit a3e0b79ebb

@ -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));

@ -9,22 +9,6 @@ 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:
@ -32,15 +16,9 @@ impl SkipPrefix for &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>)
pub trait StringType:
AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix
{
}
pub trait StringType: AsRef<str> + From<&'static str> + Into<TString<'static>> {}
impl<T> StringType for T where
T: AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix
{
}
impl<T> StringType for T where T: AsRef<str> + From<&'static str> + Into<TString<'static>> {}
/// Unified-length String type, long enough for most simple use-cases.
pub type ShortString = String<50>;
@ -122,6 +100,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> {
@ -191,22 +186,3 @@ impl<'a, 'b> PartialEq<TString<'a>> 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..]),
}
}
}

@ -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,

@ -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<T: StringType + Clone> {
op_layout: OpTextLayout<T>,
pub struct FormattedText {
op_layout: OpTextLayout<'static>,
vertical: Alignment,
char_offset: usize,
y_offset: i16,
}
impl<T: StringType + Clone> FormattedText<T> {
pub fn new(op_layout: OpTextLayout<T>) -> Self {
impl FormattedText {
pub fn new(op_layout: OpTextLayout<'static>) -> Self {
Self {
op_layout,
vertical: Alignment::Start,
@ -54,7 +51,7 @@ impl<T: StringType + Clone> FormattedText<T> {
}
// Pagination
impl<T: StringType + Clone> Paginate for FormattedText<T> {
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<T: StringType + Clone> Paginate for FormattedText<T> {
}
}
impl<T: StringType + Clone> Component for FormattedText<T> {
impl Component for FormattedText {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
@ -145,7 +142,7 @@ impl<T: StringType + Clone> Component for FormattedText<T> {
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T: StringType + Clone> FormattedText<T> {
impl FormattedText {
/// Is the same as layout_content, but does not use `&mut self`
/// to be compatible with `trace`.
/// Therefore it has to do the `clone` of `op_layout`.
@ -159,7 +156,7 @@ impl<T: StringType + Clone> FormattedText<T> {
}
#[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) {
use crate::ui::component::text::layout::trace::TraceSink;
use core::cell::Cell;

@ -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<T: StringType + Clone> {
pub struct OpTextLayout<'a> {
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 {
Self {
layout: TextLayout::new(style),
@ -115,7 +115,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
// (just for incomplete texts that were separated)
self.layout.continues_from_prev_page = continued;
let fit = self.layout.layout_text(text.as_ref(), cursor, sink);
let fit = text.map(|t| self.layout.layout_text(t, cursor, sink));
match fit {
LayoutFit::Fitting {
@ -147,9 +147,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
/// 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<Item = Op<T>> + 'b
fn filter_skipped_ops<'b, I>(
ops_iter: I,
skip_bytes: usize,
) -> impl Iterator<Item = Op<'a>> + 'b
where
I: Iterator<Item = &'b Op<T>> + 'b,
I: Iterator<Item = &'b Op<'a>> + 'b,
'a: 'b,
{
let mut skipped = 0;
@ -157,7 +160,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
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.map(str::len));
if skipped > skip_bytes {
// Fits partially
// Skipping some bytes at the beginning, leaving rest
@ -186,15 +189,15 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
}
// Op-adding operations
impl<T: StringType + Clone> OpTextLayout<T> {
pub fn with_new_item(mut self, item: Op<T>) -> 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))
}
@ -236,21 +239,21 @@ impl<T: StringType + Clone> OpTextLayout<T> {
}
// Op-adding aggregation operations
impl<T: StringType + Clone> OpTextLayout<T> {
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<TString<'a>>) -> 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<TString<'a>>) -> 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<TString<'a>>) -> 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<TString<'a>>) -> Self {
self.font(Font::DEMIBOLD).text(text.into())
}
pub fn chunkify_text(self, chunks: Option<(Chunks, i16)>) -> Self {
@ -263,11 +266,11 @@ impl<T: StringType + Clone> OpTextLayout<T> {
}
#[derive(Clone)]
pub enum Op<T: StringType> {
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.

@ -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<T> = Vec<Paragraph<T>, 32>;
pub type ParagraphVecShort<T> = Vec<Paragraph<T>, 8>;
pub trait ParagraphSource {
/// Determines the output type produced.
type StrType: StringType;
pub type ParagraphVecLong<'a> = Vec<Paragraph<'a>, 32>;
pub type ParagraphVecShort<'a> = Vec<Paragraph<'a>, 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<Self::StrType>;
fn at(&self, index: usize, offset: usize) -> Paragraph<'a>;
/// Number of paragraphs.
fn size(&self) -> usize;
@ -56,9 +53,9 @@ pub struct Paragraphs<T> {
source: T,
}
impl<T> Paragraphs<T>
impl<'a, T> Paragraphs<T>
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<S: StringType>(
fn dyn_change_offset(
mut area: Rect,
mut offset: PageOffset,
source: &dyn ParagraphSource<StrType = S>,
source: &dyn ParagraphSource<'_>,
visible: &mut Vec<TextLayoutProxy, MAX_LINES>,
) {
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<StrType = S>,
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<T> Component for Paragraphs<T>
impl<'a, T> Component for Paragraphs<T>
where
T: ParagraphSource,
T: ParagraphSource<'a>,
{
type Msg = Never;
@ -197,9 +194,9 @@ where
}
}
impl<T> Paginate for Paragraphs<T>
impl<'a, T> Paginate for Paragraphs<T>
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<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) {
t.string("component", "Paragraphs".into());
t.in_list("paragraphs", &|par_list| {
@ -247,9 +244,9 @@ pub mod trace {
}
#[derive(Clone, Copy)]
pub struct Paragraph<T> {
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<T> {
padding_bottom: i16,
}
impl<T> Paragraph<T> {
pub const fn new(style: &'static TextStyle, content: T) -> Self {
impl<'a> Paragraph<'a> {
pub fn new<T: Into<TString<'a>>>(style: &'static TextStyle, content: T) -> Self {
Self {
content,
content: content.into(),
style,
align: Alignment::Start,
break_after: false,
@ -301,25 +298,17 @@ impl<T> Paragraph<T> {
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<T: Into<TString<'a>>>(&mut self, content: T) {
self.content = content.into()
}
/// Copy style and replace content.
pub fn map<U>(&self, func: impl FnOnce(&T) -> U) -> Paragraph<U> {
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<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 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<S: StringType>(
fn advance(
mut self,
area: Rect,
source: &dyn ParagraphSource<StrType = S>,
source: &dyn ParagraphSource<'_>,
full_height: i16,
) -> (PageOffset, Option<Rect>, Option<TextLayoutProxy>) {
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<S: StringType>(
this_paragraph: &Paragraph<S>,
next_paragraph: &Paragraph<S>,
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<PageOffset>,
}
impl<T: ParagraphSource> PageBreakIterator<'_, T> {
fn dyn_next<S: StringType>(
impl<'a, T: ParagraphSource<'a>> PageBreakIterator<'_, T> {
fn dyn_next(
mut area: Rect,
paragraphs: &dyn ParagraphSource<StrType = S>,
paragraphs: &dyn ParagraphSource<'_>,
mut offset: PageOffset,
) -> Option<PageOffset> {
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
/// `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
/// character offset in that paragraph.
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
T: ParagraphSource,
T: ParagraphSource<'a>,
{
type Msg = Never;
@ -652,9 +639,9 @@ where
}
}
impl<T> Paginate for Checklist<T>
impl<'a, T> Paginate for Checklist<T>
where
T: ParagraphSource,
T: ParagraphSource<'a>,
{
fn page_count(&mut self) -> usize {
1
@ -664,7 +651,7 @@ where
}
#[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) {
t.component("Checklist");
t.int("current", self.current as i64);
@ -672,16 +659,13 @@ impl<T: ParagraphSource> crate::trace::Trace for Checklist<T> {
}
}
pub trait VecExt<T> {
fn add(&mut self, paragraph: Paragraph<T>) -> &mut Self;
pub trait VecExt<'a> {
fn add(&mut self, paragraph: Paragraph<'a>) -> &mut Self;
}
impl<T, const N: usize> VecExt<T> for Vec<Paragraph<T>, N>
where
T: AsRef<str>,
{
fn add(&mut self, paragraph: Paragraph<T>) -> &mut Self {
if paragraph.content.as_ref().is_empty() {
impl<'a, const N: usize> VecExt<'a> for Vec<Paragraph<'a>, 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<T: StringType, const N: usize> ParagraphSource for Vec<Paragraph<T>, N> {
type StrType = T;
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
impl<'a, const N: usize> ParagraphSource<'a> for Vec<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 {
@ -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] {
type StrType = T;
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
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<T: StringType, const N: usize> ParagraphSource for [Paragraph<T>; N] {
}
}
impl<T: StringType> ParagraphSource for Paragraph<T> {
type StrType = T;
fn at(&self, index: usize, offset: usize) -> Paragraph<Self::StrType> {
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 {

@ -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 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) }

@ -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<Self::StrType> {
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<Self::StrType> {
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)?;

@ -22,8 +22,8 @@ const QR_BORDER: i16 = 3;
pub struct AddressDetails {
qr_code: Qr,
details_view: Paragraphs<ParagraphVecShort<StrBuffer>>,
xpub_view: Frame<Paragraphs<Paragraph<StrBuffer>>, StrBuffer>,
details_view: Paragraphs<ParagraphVecShort<'static>>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>, 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 {

@ -78,7 +78,7 @@ pub struct Page<T>
where
T: StringType + Clone,
{
formatted: FormattedText<T>,
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<T>,
formatted: FormattedText,
) -> Self {
Self {
formatted,

@ -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);

@ -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<T, Error> = fn(&str) -> Result<T, Error>;
pub struct Progress<T>
where
T: StringType,
@ -33,9 +29,8 @@ where
value: u16,
loader_y_offset: i16,
indeterminate: bool,
description: Child<Paragraphs<Paragraph<T>>>,
description: Child<Paragraphs<Paragraph<'static>>>,
description_pad: Pad,
update_description: Option<UpdateDescriptionFn<T, Error>>,
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<T, Error>,
) -> 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
}

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
time::Instant,
ui::{
component::{
@ -21,15 +21,12 @@ pub enum ResultPopupMsg {
Confirmed,
}
pub struct ResultPopup<T>
where
T: StringType,
{
pub struct ResultPopup {
area: Rect,
pad: Pad,
result_anim: Child<ResultAnim>,
headline: Option<Label<&'static str>>,
text: Child<Paragraphs<Paragraph<T>>>,
text: Child<Paragraphs<Paragraph<'static>>>,
buttons: Option<Child<ButtonController>>,
autoclose: bool,
}
@ -40,13 +37,10 @@ const ANIM_POS: i16 = 32;
const ANIM_POS_ADJ_HEADLINE: i16 = 10;
const ANIM_POS_ADJ_BUTTON: i16 = 6;
impl<T> ResultPopup<T>
where
T: StringType + Clone,
{
impl ResultPopup {
pub fn new(
icon: Icon,
text: T,
text: impl Into<TString<'static>>,
headline: Option<&'static str>,
button_text: Option<&'static str>,
) -> Self {
@ -89,10 +83,7 @@ where
}
}
impl<T> Component for ResultPopup<T>
where
T: StringType + Clone,
{
impl Component for ResultPopup {
type Msg = ResultPopupMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -165,10 +156,7 @@ where
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for ResultPopup<T>
where
T: StringType + Clone,
{
impl crate::trace::Trace for ResultPopup {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ResultPopup");
t.child("text", &self.text);

@ -77,9 +77,9 @@ where
}
}
impl<T> ComponentMsgObj for Paragraphs<T>
impl<'a, T> ComponentMsgObj for Paragraphs<T>
where
T: ParagraphSource,
T: ParagraphSource<'a>,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
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::<StrBuffer>::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::<StrBuffer>::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::<StrBuffer>::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<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();
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<List> = 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::<StrBuffer>::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::<StrBuffer>::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::<StrBuffer>::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::<StrBuffer>::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::<StrBuffer>::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::<StrBuffer>::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::<Paragraphs<ParagraphVecShort<StrBuffer>>>::new(
ShowMore::<Paragraphs<ParagraphVecShort>>::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,11 +1542,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(indeterminate, description)
.with_title(title)
.with_update_description(StrBuffer::alloc),
)?;
let obj = LayoutObj::new(Progress::new(indeterminate, description).with_title(title))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }

@ -20,8 +20,8 @@ const MAX_XPUBS: usize = 16;
pub struct AddressDetails<T> {
qr_code: Frame<Qr, T>,
details: Frame<Paragraphs<ParagraphVecShort<StrBuffer>>, T>,
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>,
details: Frame<Paragraphs<ParagraphVecShort<'static>>, T>,
xpub_view: Frame<Paragraphs<Paragraph<'static>>, T>,
xpubs: Vec<(T, T), MAX_XPUBS>,
xpub_page_count: Vec<u8, MAX_XPUBS>,
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()),

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{
image::BlendedImage,
@ -91,18 +91,17 @@ where
}
}
pub struct IconDialog<T, U> {
pub struct IconDialog<U> {
image: Child<BlendedImage>,
paragraphs: Paragraphs<ParagraphVecShort<T>>,
paragraphs: Paragraphs<ParagraphVecShort<'static>>,
controls: Child<U>,
}
impl<T, U> IconDialog<T, U>
impl<U> IconDialog<U>
where
T: StringType,
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 {
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<T>) -> 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<TString<'static>>) -> 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<TString<'static>>) -> 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<TString<'static>>) -> 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<TString<'static>>; 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<T, U> Component for IconDialog<T, U>
impl<U> Component for IconDialog<U>
where
T: StringType,
U: Component,
{
type Msg = DialogMsg<Never, U::Msg>;
@ -207,9 +205,8 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for IconDialog<T, U>
impl<U> crate::trace::Trace for IconDialog<U>
where
T: StringType,
U: crate::trace::Trace,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {

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

@ -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);

@ -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<T> {
value: u16,
loader_y_offset: i16,
indeterminate: bool,
description: Child<Paragraphs<Paragraph<T>>>,
description: Child<Paragraphs<Paragraph<'static>>>,
description_pad: Pad,
update_description: fn(&str) -> Result<T, Error>,
}
impl<T> Progress<T>
@ -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<T, Error>,
) -> 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();

@ -128,9 +128,8 @@ where
}
}
impl<T, U> ComponentMsgObj for IconDialog<T, U>
impl<U> ComponentMsgObj for IconDialog<U>
where
T: StringType,
U: Component,
<U as Component>::Msg: TryInto<Obj, Error = Error>,
{
@ -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<T> ComponentMsgObj for Paragraphs<T>
impl<'a, T> ComponentMsgObj for Paragraphs<T>
where
T: ParagraphSource,
T: ParagraphSource<'a>,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!()
}
}
impl<T> ComponentMsgObj for FormattedText<T>
where
T: StringType + Clone,
{
impl ComponentMsgObj for FormattedText {
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
unreachable!()
}
}
impl<T> ComponentMsgObj for Checklist<T>
impl<'a, T> ComponentMsgObj for Checklist<T>
where
T: ParagraphSource,
T: ParagraphSource<'a>,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
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
T: ParagraphSource,
S: StringType + Clone,
T: ParagraphSource<'a>,
{
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
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<StrBuffer>; 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::<StrBuffer>::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(
@ -1549,17 +1537,11 @@ extern "C" fn new_show_progress(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()?;
let indeterminate: bool = kwargs.get_or(Qstr::MP_QSTR_indeterminate, false)?;
let description: StrBuffer =
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
let description: TString = kwargs.get_or(Qstr::MP_QSTR_description, TString::empty())?;
// 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))?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }

Loading…
Cancel
Save