mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 07:28:10 +00:00
refactor(core/rust): Paragraph is now based on TString
This commit is contained in:
parent
9c287adf64
commit
39b7b22777
@ -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,38 +9,18 @@ use crate::micropython::{buffer::StrBuffer, obj::Obj};
|
||||
#[cfg(feature = "translations")]
|
||||
use crate::translations::TR;
|
||||
|
||||
/// Trait for slicing off string prefix by a specified number of bytes.
|
||||
/// See `StringType` for deeper explanation.
|
||||
pub trait SkipPrefix {
|
||||
fn skip_prefix(&self, bytes: usize) -> Self;
|
||||
}
|
||||
|
||||
// XXX only implemented in bootloader, as we don't want &str to satisfy
|
||||
// StringType in the main firmware. This is because we want to avoid duplication
|
||||
// of every StringType-parametrized component.
|
||||
#[cfg(feature = "bootloader")]
|
||||
impl SkipPrefix for &str {
|
||||
fn skip_prefix(&self, chars: usize) -> Self {
|
||||
&self[chars..]
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for internal representation of strings.
|
||||
/// Exists so that we can support `StrBuffer` as well as `&str` in the UI
|
||||
/// components. Implies the following operations:
|
||||
/// - dereference into a short-lived `&str` reference (AsRef<str>)
|
||||
/// - create a new string by skipping some number of bytes (SkipPrefix) - used
|
||||
/// when rendering continuations of long strings
|
||||
/// Trait for internal representation of strings. This is a legacy crutch before
|
||||
/// we fully transition to `TString`. For now, it allows some manner of
|
||||
/// compatibility between `&str` and `StrBuffer`. Implies the following
|
||||
/// operations:
|
||||
/// - dereference into a short-lived `&str` reference (AsRef<str>) (probably not
|
||||
/// strictly necessary anymore)
|
||||
/// - create a new string from a string literal (From<&'static str>)
|
||||
pub trait StringType:
|
||||
AsRef<str> + From<&'static str> + Into<TString<'static>> + SkipPrefix
|
||||
{
|
||||
}
|
||||
/// - infallibly convert into a `TString` (Into<TString<'static>>), which is
|
||||
/// then used for other operations.
|
||||
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>;
|
||||
@ -129,6 +109,23 @@ impl TString<'_> {
|
||||
Self::Str(s) => fun(s),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_prefix(&self, skip_bytes: usize) -> Self {
|
||||
self.map(|s| {
|
||||
assert!(skip_bytes <= s.len());
|
||||
assert!(s.is_char_boundary(skip_bytes));
|
||||
});
|
||||
match self {
|
||||
#[cfg(feature = "micropython")]
|
||||
Self::Allocated(s) => Self::Allocated(s.skip_prefix(skip_bytes)),
|
||||
#[cfg(feature = "translations")]
|
||||
Self::Translation { tr, offset } => Self::Translation {
|
||||
tr: *tr,
|
||||
offset: offset + skip_bytes as u16,
|
||||
},
|
||||
Self::Str(s) => Self::Str(&s[skip_bytes..]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TString<'static> {
|
||||
@ -198,22 +195,3 @@ impl<'a, 'b> PartialEq<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> 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),
|
||||
@ -116,7 +116,7 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<T> {
|
||||
// (just for incomplete texts that were separated)
|
||||
layout.continues_from_prev_page = continued;
|
||||
|
||||
let fit = layout.layout_text(text.as_ref(), cursor, sink);
|
||||
let fit = text.map(|t| layout.layout_text(t, cursor, sink));
|
||||
|
||||
match fit {
|
||||
LayoutFit::Fitting {
|
||||
@ -148,9 +148,12 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<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;
|
||||
@ -158,7 +161,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.len());
|
||||
if skipped > skip_bytes {
|
||||
// Fits partially
|
||||
// Skipping some bytes at the beginning, leaving rest
|
||||
@ -187,15 +190,15 @@ impl<'a, T: StringType + Clone + 'a> OpTextLayout<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))
|
||||
}
|
||||
|
||||
@ -237,21 +240,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 {
|
||||
@ -264,11 +267,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 {
|
||||
let mut page = 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
|
||||
}
|
||||
|
@ -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,8 +1542,7 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
||||
.and_then(Obj::try_into_option)
|
||||
.unwrap_or(None);
|
||||
|
||||
let mut progress =
|
||||
Progress::new(indeterminate, description).with_update_description(StrBuffer::alloc);
|
||||
let mut progress = Progress::new(indeterminate, description);
|
||||
if let Some(title) = title {
|
||||
progress = progress.with_title(title);
|
||||
};
|
||||
|
@ -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(
|
||||
@ -1562,12 +1550,7 @@ extern "C" fn new_show_progress(n_args: usize, args: *const Obj, kwargs: *mut Ma
|
||||
|
||||
// Description updates are received as &str and we need to provide a way to
|
||||
// convert them to StrBuffer.
|
||||
let obj = LayoutObj::new(Progress::new(
|
||||
title,
|
||||
indeterminate,
|
||||
description,
|
||||
StrBuffer::alloc,
|
||||
))?;
|
||||
let obj = LayoutObj::new(Progress::new(title, indeterminate, description.into()))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||
|
Loading…
Reference in New Issue
Block a user