1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-10 15:30:55 +00:00

fix(core): rebase on drawlib and TStringificaton

[no changelog]
This commit is contained in:
obrusvit 2024-04-11 15:10:30 +02:00 committed by Martin Milata
parent 8978f36096
commit ac39b026cf
21 changed files with 526 additions and 618 deletions

View File

@ -9,5 +9,5 @@
pub use super::model_mercury::constant::*; pub use super::model_mercury::constant::*;
#[cfg(all(feature = "model_tr", not(feature = "model_tt")))] #[cfg(all(feature = "model_tr", not(feature = "model_tt")))]
pub use super::model_tr::constant::*; pub use super::model_tr::constant::*;
#[cfg(all(feature = "model_tt", not(feature = "model_mercury")))] #[cfg(feature = "model_tt")]
pub use super::model_tt::constant::*; pub use super::model_tt::constant::*;

View File

@ -2,8 +2,7 @@ use heapless::Vec;
use crate::{ use crate::{
error::Error, error::Error,
micropython::buffer::StrBuffer, strutil::TString,
strutil::StringType,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
@ -19,58 +18,57 @@ use super::{theme, Frame, FrameMsg};
const MAX_XPUBS: usize = 16; const MAX_XPUBS: usize = 16;
pub struct AddressDetails<T> { pub struct AddressDetails {
qr_code: Frame<Qr, T>, qr_code: Frame<Qr>,
details: Frame<Paragraphs<ParagraphVecShort<StrBuffer>>, T>, details: Frame<Paragraphs<ParagraphVecShort<'static>>>,
xpub_view: Frame<Paragraphs<Paragraph<T>>, T>, xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
xpubs: Vec<(T, T), MAX_XPUBS>, xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>,
xpub_page_count: Vec<u8, MAX_XPUBS>, xpub_page_count: Vec<u8, MAX_XPUBS>,
current_page: usize, current_page: usize,
} }
impl<T> AddressDetails<T> impl AddressDetails {
where
T: StringType,
{
pub fn new( pub fn new(
qr_title: T, qr_title: TString<'static>,
qr_address: T, qr_address: TString<'static>,
case_sensitive: bool, case_sensitive: bool,
details_title: T, details_title: TString<'static>,
account: Option<StrBuffer>, account: Option<TString<'static>>,
path: Option<StrBuffer>, path: Option<TString<'static>>,
) -> Result<Self, Error> ) -> Result<Self, Error> {
where
T: From<&'static str>,
{
let mut para = ParagraphVecShort::new(); let mut para = ParagraphVecShort::new();
if let Some(a) = account { if let Some(a) = account {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
TR::words__account_colon.try_into()?, TR::words__account_colon,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, a)); para.add(Paragraph::new(&theme::TEXT_MONO, a));
} }
if let Some(p) = path { if let Some(p) = path {
para.add(Paragraph::new( para.add(Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
TR::address_details__derivation_path.try_into()?, TR::address_details__derivation_path,
)); ));
para.add(Paragraph::new(&theme::TEXT_MONO, p)); para.add(Paragraph::new(&theme::TEXT_MONO, p));
} }
let result = Self { let result = Self {
qr_code: Frame::left_aligned( qr_code: Frame::left_aligned(
qr_title, qr_title,
Qr::new(qr_address, case_sensitive)?.with_border(7), qr_address
.map(|s| Qr::new(s, case_sensitive))?
.with_border(7),
) )
.with_cancel_button() .with_cancel_button()
.with_border(theme::borders_horizontal_scroll()), .with_border(theme::borders_horizontal_scroll()),
details: Frame::left_aligned(details_title, para.into_paragraphs()) details: Frame::left_aligned(
details_title,
para.into_paragraphs(),
)
.with_cancel_button() .with_cancel_button()
.with_border(theme::borders_horizontal_scroll()), .with_border(theme::borders_horizontal_scroll()),
xpub_view: Frame::left_aligned( xpub_view: Frame::left_aligned(
" \n ".into(), " \n ".into(),
Paragraph::new(&theme::TEXT_MONO, "".into()).into_paragraphs(), Paragraph::new(&theme::TEXT_MONO, "").into_paragraphs(),
) )
.with_cancel_button() .with_cancel_button()
.with_border(theme::borders_horizontal_scroll()), .with_border(theme::borders_horizontal_scroll()),
@ -81,24 +79,24 @@ where
Ok(result) Ok(result)
} }
pub fn add_xpub(&mut self, title: T, xpub: T) -> Result<(), Error> { pub fn add_xpub(
&mut self,
title: TString<'static>,
xpub: TString<'static>,
) -> Result<(), Error> {
self.xpubs self.xpubs
.push((title, xpub)) .push((title, xpub))
.map_err(|_| Error::OutOfRange) .map_err(|_| Error::OutOfRange)
} }
fn switch_xpub(&mut self, i: usize, page: usize) -> usize fn switch_xpub(&mut self, i: usize, page: usize) -> usize {
where
T: Clone,
{
// Context is needed for updating child so that it can request repaint. In this // Context is needed for updating child so that it can request repaint. In this
// case the parent component that handles paging always requests complete // case the parent component that handles paging always requests complete
// repaint after page change so we can use a dummy context here. // repaint after page change so we can use a dummy context here.
let mut dummy_ctx = EventCtx::new(); let mut dummy_ctx = EventCtx::new();
self.xpub_view self.xpub_view.update_title(&mut dummy_ctx, self.xpubs[i].0);
.update_title(&mut dummy_ctx, self.xpubs[i].0.clone());
self.xpub_view.update_content(&mut dummy_ctx, |p| { self.xpub_view.update_content(&mut dummy_ctx, |p| {
p.inner_mut().update(self.xpubs[i].1.clone()); p.inner_mut().update(self.xpubs[i].1);
let npages = p.page_count(); let npages = p.page_count();
p.change_page(page); p.change_page(page);
npages npages
@ -123,10 +121,7 @@ where
} }
} }
impl<T> Paginate for AddressDetails<T> impl Paginate for AddressDetails {
where
T: StringType + Clone,
{
fn page_count(&mut self) -> usize { fn page_count(&mut self) -> usize {
let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum(); let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum();
2usize.saturating_add(total_xpub_pages.into()) 2usize.saturating_add(total_xpub_pages.into())
@ -142,10 +137,7 @@ where
} }
} }
impl<T> Component for AddressDetails<T> impl Component for AddressDetails {
where
T: StringType + Clone,
{
type Msg = (); type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -201,10 +193,7 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for AddressDetails<T> impl crate::trace::Trace for AddressDetails {
where
T: StringType,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("AddressDetails"); t.component("AddressDetails");
match self.current_page { match self.current_page {

View File

@ -32,7 +32,7 @@ pub struct Button {
long_timer: Option<TimerToken>, long_timer: Option<TimerToken>,
} }
impl<T> Button<T> { impl Button {
/// Offsets the baseline of the button text /// Offsets the baseline of the button text
/// -x/+x => left/right /// -x/+x => left/right
/// -y/+y => up/down /// -y/+y => up/down
@ -58,8 +58,8 @@ impl<T> Button<T> {
Self::new(ButtonContent::Icon(icon)) Self::new(ButtonContent::Icon(icon))
} }
pub const fn with_icon_and_text(content: IconText<T>) -> Self { pub const fn with_icon_and_text(content: IconText) -> Self {
Self::new(ButtonContent::IconAndText::<T>(content)) Self::new(ButtonContent::IconAndText(content))
} }
pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self { pub const fn with_icon_blend(bg: Icon, fg: Icon, fg_offset: Offset) -> Self {
@ -204,8 +204,8 @@ impl<T> Button<T> {
match &self.content { match &self.content {
ButtonContent::Empty => {} ButtonContent::Empty => {}
ButtonContent::Text(text) => { ButtonContent::Text(text) => {
let text = text.as_ref();
let start_of_baseline = self.area.center() + Self::BASELINE_OFFSET; let start_of_baseline = self.area.center() + Self::BASELINE_OFFSET;
text.map(|text| {
display::text_left( display::text_left(
start_of_baseline, start_of_baseline,
text, text,
@ -213,6 +213,7 @@ impl<T> Button<T> {
style.text_color, style.text_color,
style.button_color, style.button_color,
); );
});
} }
ButtonContent::Icon(icon) => { ButtonContent::Icon(icon) => {
icon.draw( icon.draw(
@ -238,12 +239,13 @@ impl<T> Button<T> {
match &self.content { match &self.content {
ButtonContent::Empty => {} ButtonContent::Empty => {}
ButtonContent::Text(text) => { ButtonContent::Text(text) => {
let text = text.as_ref();
let start_of_baseline = self.area.left_center() + Self::BASELINE_OFFSET; let start_of_baseline = self.area.left_center() + Self::BASELINE_OFFSET;
text.map(|text| {
shape::Text::new(start_of_baseline, text) shape::Text::new(start_of_baseline, text)
.with_font(style.font) .with_font(style.font)
.with_fg(style.text_color) .with_fg(style.text_color)
.render(target); .render(target);
});
} }
ButtonContent::Icon(icon) => { ButtonContent::Icon(icon) => {
shape::ToifImage::new(self.area.center(), icon.toif) shape::ToifImage::new(self.area.center(), icon.toif)
@ -376,7 +378,7 @@ impl crate::trace::Trace for Button {
ButtonContent::Text(text) => t.string("text", *text), ButtonContent::Text(text) => t.string("text", *text),
ButtonContent::Icon(_) => t.bool("icon", true), ButtonContent::Icon(_) => t.bool("icon", true),
ButtonContent::IconAndText(content) => { ButtonContent::IconAndText(content) => {
t.string("text", content.text.as_ref().into()); t.string("text", content.text.into());
t.bool("icon", true); t.bool("icon", true);
} }
ButtonContent::IconBlend(_, _, _) => t.bool("icon", true), ButtonContent::IconBlend(_, _, _) => t.bool("icon", true),
@ -397,7 +399,7 @@ pub enum ButtonContent {
Empty, Empty,
Text(TString<'static>), Text(TString<'static>),
Icon(Icon), Icon(Icon),
IconAndText(IconText<T>), IconAndText(IconText),
IconBlend(Icon, Icon, Offset), IconBlend(Icon, Icon, Offset),
} }
@ -419,18 +421,15 @@ pub struct ButtonStyle {
pub border_width: i16, pub border_width: i16,
} }
impl<T> Button<T> { impl Button {
pub fn cancel_confirm( pub fn cancel_confirm(
left: Button<T>, left: Button,
right: Button<T>, right: Button,
left_is_small: bool, left_is_small: bool,
) -> CancelConfirm< ) -> CancelConfirm<
T,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
> >
where
T: AsRef<str>,
{ {
let width = if left_is_small { let width = if left_is_small {
theme::BUTTON_WIDTH theme::BUTTON_WIDTH
@ -450,21 +449,18 @@ impl<T> Button<T> {
} }
pub fn cancel_confirm_text( pub fn cancel_confirm_text(
left: Option<T>, left: Option<TString<'static>>,
right: Option<T>, right: Option<TString<'static>>,
) -> CancelConfirm< ) -> CancelConfirm<
T,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
> >
where
T: AsRef<str>,
{ {
let left_is_small: bool; let left_is_small: bool;
let left = if let Some(verb) = left { let left = if let Some(verb) = left {
left_is_small = verb.as_ref().len() <= 4; left_is_small = verb.len() <= 4;
if verb.as_ref() == "^" { if verb == "^".into() {
Button::with_icon(theme::ICON_UP) Button::with_icon(theme::ICON_UP)
} else { } else {
Button::with_text(verb) Button::with_text(verb)
@ -482,16 +478,13 @@ impl<T> Button<T> {
} }
pub fn cancel_info_confirm( pub fn cancel_info_confirm(
confirm: T, confirm: TString<'static>,
info: T, info: TString<'static>,
) -> CancelInfoConfirm< ) -> CancelInfoConfirm<
T,
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>, impl Fn(ButtonMsg) -> Option<CancelInfoConfirmMsg>,
> >
where
T: AsRef<str>,
{ {
let right = Button::with_text(confirm) let right = Button::with_text(confirm)
.styled(theme::button_confirm()) .styled(theme::button_confirm())
@ -522,11 +515,10 @@ pub enum CancelConfirmMsg {
Confirmed, Confirmed,
} }
type CancelInfoConfirm<T, F0, F1, F2> = FixedHeightBar< type CancelInfoConfirm<F0, F1, F2> =
Split<MsgMap<Button<T>, F0>, Split<MsgMap<Button<T>, F1>, MsgMap<Button<T>, F2>>>, FixedHeightBar<Split<MsgMap<Button, F0>, Split<MsgMap<Button, F1>, MsgMap<Button, F2>>>>;
>;
type CancelConfirm<T, F0, F1> = FixedHeightBar<Split<MsgMap<Button<T>, F0>, MsgMap<Button<T>, F1>>>; type CancelConfirm<F0, F1> = FixedHeightBar<Split<MsgMap<Button, F0>, MsgMap<Button, F1>>>;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum CancelInfoConfirmMsg { pub enum CancelInfoConfirmMsg {
@ -536,25 +528,23 @@ pub enum CancelInfoConfirmMsg {
} }
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub struct IconText<T> { pub struct IconText {
text: T, text: &'static str,
icon: Icon, icon: Icon,
} }
impl<T> IconText<T> impl IconText {
where
T: AsRef<str>,
{
const ICON_SPACE: i16 = 46; const ICON_SPACE: i16 = 46;
const ICON_MARGIN: i16 = 4; const ICON_MARGIN: i16 = 4;
const TEXT_MARGIN: i16 = 6; const TEXT_MARGIN: i16 = 6;
pub fn new(text: T, icon: Icon) -> Self { pub fn new(text: &'static str, icon: Icon) -> Self {
Self { text, icon } Self { text, icon }
} }
pub fn paint(&self, area: Rect, style: &ButtonStyle, baseline_offset: Offset) { pub fn paint(&self, area: Rect, style: &ButtonStyle, baseline_offset: Offset) {
let width = style.font.text_width(self.text.as_ref()); let width = style.font.text_width(self.text);
let height = style.font.text_height();
let mut use_icon = false; let mut use_icon = false;
let mut use_text = false; let mut use_text = false;
@ -563,7 +553,8 @@ where
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2), area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
area.center().y, area.center().y,
); );
let mut text_pos = area.left_center() + baseline_offset; let mut text_pos =
area.center() + Offset::new(-width / 2, height / 2) + baseline_offset;
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) { if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
//display both icon and text //display both icon and text
@ -581,7 +572,7 @@ where
if use_text { if use_text {
display::text_left( display::text_left(
text_pos, text_pos,
self.text.as_ref(), self.text,
style.font, style.font,
style.text_color, style.text_color,
style.button_color, style.button_color,
@ -629,7 +620,7 @@ where
} }
if use_text { if use_text {
shape::Text::new(text_pos, self.text.as_ref()) shape::Text::new(text_pos, self.text)
.with_fg(style.text_color) .with_fg(style.text_color)
.render(target); .render(target);
} }

View File

@ -3,15 +3,15 @@ use core::mem;
use crate::{ use crate::{
error::Error, error::Error,
maybe_trace::MaybeTrace, maybe_trace::MaybeTrace,
micropython::buffer::StrBuffer, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
base::Never, Bar, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split, base::Never, Bar, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split,
}, },
constant,
display::loader::{loader_circular_uncompress, LoaderDimensions}, display::loader::{loader_circular_uncompress, LoaderDimensions},
geometry::{Insets, Offset, Rect}, geometry::{Insets, Offset, Rect},
model_mercury::constant,
shape, shape,
shape::Renderer, shape::Renderer,
util::animation_disabled, util::animation_disabled,
@ -27,31 +27,22 @@ const LOADER_INNER: i16 = 28;
const LOADER_OFFSET: i16 = -34; const LOADER_OFFSET: i16 = -34;
const LOADER_SPEED: u16 = 5; const LOADER_SPEED: u16 = 5;
pub struct CoinJoinProgress<T, U> { pub struct CoinJoinProgress<U> {
value: u16, value: u16,
indeterminate: bool, indeterminate: bool,
content: Child<Frame<Split<Empty, U>, StrBuffer>>, content: Child<Frame<Split<Empty, U>>>,
// Label is not a child since circular loader paints large black rectangle which overlaps it. // Label is not a child since circular loader paints large black rectangle which overlaps it.
// To work around this, draw label every time loader is drawn. // To work around this, draw label every time loader is drawn.
label: Label<T>, label: Label<'static>,
} }
impl<T, U> CoinJoinProgress<T, U> impl<U> CoinJoinProgress<U> {
where
T: AsRef<str>,
{
pub fn new( pub fn new(
text: T, text: TString<'static>,
indeterminate: bool, indeterminate: bool,
) -> Result<CoinJoinProgress<T, impl Component<Msg = Never> + MaybeTrace>, Error> ) -> Result<CoinJoinProgress<impl Component<Msg = Never> + MaybeTrace>, Error> {
where
T: AsRef<str>,
{
let style = theme::label_coinjoin_progress(); let style = theme::label_coinjoin_progress();
let label = Label::centered( let label = Label::centered(TR::coinjoin__title_do_not_disconnect.into(), style)
TryInto::<StrBuffer>::try_into(TR::coinjoin__title_do_not_disconnect)?,
style,
)
.vertically_centered(); .vertically_centered();
let bg = Bar::new(style.background_color, theme::BG, 2); let bg = Bar::new(style.background_color, theme::BG, 2);
let inner = (bg, label); let inner = (bg, label);
@ -59,17 +50,20 @@ where
} }
} }
impl<T, U> CoinJoinProgress<T, U> impl<U> CoinJoinProgress<U>
where where
T: AsRef<str>,
U: Component<Msg = Never>, U: Component<Msg = Never>,
{ {
pub fn with_background(text: T, inner: U, indeterminate: bool) -> Result<Self, Error> { pub fn with_background(
text: TString<'static>,
inner: U,
indeterminate: bool,
) -> Result<Self, Error> {
Ok(Self { Ok(Self {
value: 0, value: 0,
indeterminate, indeterminate,
content: Frame::centered( content: Frame::centered(
TR::coinjoin__title_progress.try_into()?, TR::coinjoin__title_progress.into(),
Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner), Split::bottom(RECTANGLE_HEIGHT, 0, Empty, inner),
) )
.into_child(), .into_child(),
@ -78,9 +72,8 @@ where
} }
} }
impl<T, U> Component for CoinJoinProgress<T, U> impl<U> Component for CoinJoinProgress<U>
where where
T: AsRef<str>,
U: Component<Msg = Never>, U: Component<Msg = Never>,
{ {
type Msg = Never; type Msg = Never;
@ -167,9 +160,8 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for CoinJoinProgress<T, U> impl<U> crate::trace::Trace for CoinJoinProgress<U>
where where
T: AsRef<str>,
U: Component + crate::trace::Trace, U: Component + crate::trace::Trace,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

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

View File

@ -1,4 +1,6 @@
use crate::ui::{ use crate::{
strutil::TString,
ui::{
component::{image::Image, Child, Component, Event, EventCtx, Label}, component::{image::Image, Child, Component, Event, EventCtx, Label},
display, display,
geometry::{Insets, Rect}, geometry::{Insets, Rect},
@ -9,6 +11,7 @@ use crate::ui::{
}, },
shape, shape,
shape::Renderer, shape::Renderer,
},
}; };
use super::CancelConfirmMsg; use super::CancelConfirmMsg;
@ -25,10 +28,10 @@ pub enum FidoMsg {
Cancelled, Cancelled,
} }
pub struct FidoConfirm<F: Fn(usize) -> T, T, U> { pub struct FidoConfirm<F: Fn(usize) -> TString<'static>, U> {
page_swipe: Swipe, page_swipe: Swipe,
app_name: Label<T>, app_name: Label<'static>,
account_name: Label<T>, account_name: Label<'static>,
icon: Child<Image>, icon: Child<Image>,
/// Function/closure that will return appropriate page on demand. /// Function/closure that will return appropriate page on demand.
get_account: F, get_account: F,
@ -37,20 +40,19 @@ pub struct FidoConfirm<F: Fn(usize) -> T, T, U> {
controls: U, controls: U,
} }
impl<F, T, U> FidoConfirm<F, T, U> impl<F, U> FidoConfirm<F, U>
where where
F: Fn(usize) -> T, F: Fn(usize) -> TString<'static>,
T: AsRef<str> + From<&'static str>,
U: Component<Msg = CancelConfirmMsg>, U: Component<Msg = CancelConfirmMsg>,
{ {
pub fn new( pub fn new(
app_name: T, app_name: TString<'static>,
get_account: F, get_account: F,
page_count: usize, page_count: usize,
icon_name: Option<T>, icon_name: Option<TString<'static>>,
controls: U, controls: U,
) -> Self { ) -> Self {
let icon_data = get_fido_icon_data(icon_name.as_ref()); let icon_data = get_fido_icon_data(icon_name);
// Preparing scrollbar and setting its page-count. // Preparing scrollbar and setting its page-count.
let mut scrollbar = ScrollBar::horizontal(); let mut scrollbar = ScrollBar::horizontal();
@ -118,10 +120,9 @@ where
} }
} }
impl<F, T, U> Component for FidoConfirm<F, T, U> impl<F, U> Component for FidoConfirm<F, U>
where where
F: Fn(usize) -> T, F: Fn(usize) -> TString<'static>,
T: AsRef<str> + From<&'static str>,
U: Component<Msg = CancelConfirmMsg>, U: Component<Msg = CancelConfirmMsg>,
{ {
type Msg = FidoMsg; type Msg = FidoMsg;
@ -200,8 +201,8 @@ where
// Account name is optional. // Account name is optional.
// Showing it only if it differs from app name. // Showing it only if it differs from app name.
// (Dummy requests usually have some text as both app_name and account_name.) // (Dummy requests usually have some text as both app_name and account_name.)
let account_name = self.account_name.text().as_ref(); let account_name = self.account_name.text();
let app_name = self.app_name.text().as_ref(); let app_name = self.app_name.text();
if !account_name.is_empty() && account_name != app_name { if !account_name.is_empty() && account_name != app_name {
self.account_name.paint(); self.account_name.paint();
} }
@ -230,8 +231,8 @@ where
// Account name is optional. // Account name is optional.
// Showing it only if it differs from app name. // Showing it only if it differs from app name.
// (Dummy requests usually have some text as both app_name and account_name.) // (Dummy requests usually have some text as both app_name and account_name.)
let account_name = self.account_name.text().as_ref(); let account_name = self.account_name.text();
let app_name = self.app_name.text().as_ref(); let app_name = self.app_name.text();
if !account_name.is_empty() && account_name != app_name { if !account_name.is_empty() && account_name != app_name {
self.account_name.render(target); self.account_name.render(target);
} }
@ -251,9 +252,9 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<F, T, U> crate::trace::Trace for FidoConfirm<F, T, U> impl<F, T> crate::trace::Trace for FidoConfirm<F, T>
where where
F: Fn(usize) -> T, F: Fn(usize) -> TString<'static>,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("FidoConfirm"); t.component("FidoConfirm");

View File

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

View File

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

View File

@ -62,7 +62,7 @@ where
self self
} }
pub fn with_subtitle(mut self, subtitle: U) -> Self { pub fn with_subtitle(mut self, subtitle: TString<'static>) -> Self {
let style = theme::TEXT_SUB; let style = theme::TEXT_SUB;
self.title = Child::new(self.title.into_inner().top_aligned()); self.title = Child::new(self.title.into_inner().top_aligned());
self.subtitle = Some(Child::new(Label::new( self.subtitle = Some(Child::new(Label::new(

View File

@ -288,6 +288,13 @@ impl Component for Homescreen {
} }
self.label.map(|t| { self.label.map(|t| {
let r = Rect::new(Point::new(6, 198), Point::new(234, 233));
shape::Bar::new(r)
.with_bg(Color::black())
.with_alpha(89)
.with_radius(3)
.render(target);
let style = theme::TEXT_DEMIBOLD; let style = theme::TEXT_DEMIBOLD;
let pos = Point::new(self.pad.area.center().x, LABEL_Y); let pos = Point::new(self.pad.area.center().x, LABEL_Y);
shape::Text::new(pos, t) shape::Text::new(pos, t)
@ -355,15 +362,15 @@ impl crate::trace::Trace for Homescreen {
} }
} }
pub struct Lockscreen { pub struct Lockscreen<'a> {
label: TString<'static>, label: TString<'a>,
custom_image: Option<Gc<[u8]>>, custom_image: Option<Gc<[u8]>>,
bootscreen: bool, bootscreen: bool,
coinjoin_authorized: bool, coinjoin_authorized: bool,
} }
impl Lockscreen { impl<'a> Lockscreen<'a> {
pub fn new(label: TString<'static>, bootscreen: bool, coinjoin_authorized: bool) -> Self { pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
Lockscreen { Lockscreen {
label, label,
custom_image: get_user_custom_image().ok(), custom_image: get_user_custom_image().ok(),
@ -373,7 +380,7 @@ impl Lockscreen {
} }
} }
impl Component for Lockscreen { impl Component for Lockscreen<'_> {
type Msg = HomescreenMsg; type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -470,7 +477,7 @@ impl Component for Lockscreen {
shape::JpegImage::new(center, img_data) shape::JpegImage::new(center, img_data)
.with_align(Alignment2D::CENTER) .with_align(Alignment2D::CENTER)
.with_blur(4) .with_blur(4)
.with_dim(130) .with_dim(140)
.render(target); .render(target);
} else if is_image_toif(img_data) { } else if is_image_toif(img_data) {
shape::ToifImage::new(center, unwrap!(Toif::new(img_data))) shape::ToifImage::new(center, unwrap!(Toif::new(img_data)))
@ -591,7 +598,7 @@ fn is_image_toif(buffer: &[u8]) -> bool {
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Lockscreen { impl crate::trace::Trace for Lockscreen<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Lockscreen"); t.component("Lockscreen");
} }

View File

@ -23,9 +23,9 @@ use heapless::String;
const MAX_LENGTH: usize = 8; const MAX_LENGTH: usize = 8;
pub struct Bip39Input { pub struct Bip39Input {
button: Button<&'static str>, button: Button<>,
// used only to keep track of suggestion text color // used only to keep track of suggestion text color
button_suggestion: Button<&'static str>, button_suggestion: Button<>,
textbox: TextBox<MAX_LENGTH>, textbox: TextBox<MAX_LENGTH>,
multi_tap: MultiTapKeyboard, multi_tap: MultiTapKeyboard,
options_num: Option<usize>, options_num: Option<usize>,
@ -228,7 +228,7 @@ impl Bip39Input {
// Styling the input to reflect already filled word // Styling the input to reflect already filled word
Self { Self {
button: Button::with_icon(theme::ICON_LIST_CHECK).styled(theme::button_pin_confirm()), button: Button::with_icon(theme::ICON_LIST_CHECK).styled(theme::button_pin_confirm()),
textbox: TextBox::new(String::from(word)), textbox: TextBox::new(unwrap!(String::try_from(word))),
multi_tap: MultiTapKeyboard::new(), multi_tap: MultiTapKeyboard::new(),
options_num: bip39::options_num(word), options_num: bip39::options_num(word),
suggested_word: bip39::complete_word(word), suggested_word: bip39::complete_word(word),
@ -310,7 +310,7 @@ impl Bip39Input {
// Disabled button. // Disabled button.
self.button.disable(ctx); self.button.disable(ctx);
self.button.set_stylesheet(ctx, theme::button_pin()); self.button.set_stylesheet(ctx, theme::button_pin());
self.button.set_content(ctx, ButtonContent::Text("")); self.button.set_content(ctx, ButtonContent::Text("".into()));
} }
} }
} }

View File

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

View File

@ -1,4 +1,6 @@
use crate::ui::{ use crate::{
strutil::TString,
ui::{
component::{ component::{
base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Never, base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Never,
}, },
@ -13,6 +15,7 @@ use crate::ui::{
shape, shape,
shape::Renderer, shape::Renderer,
util::long_line_content_with_ellipsis, util::long_line_content_with_ellipsis,
},
}; };
use core::cell::Cell; use core::cell::Cell;
@ -25,9 +28,9 @@ pub enum PassphraseKeyboardMsg {
pub struct PassphraseKeyboard { pub struct PassphraseKeyboard {
page_swipe: Swipe, page_swipe: Swipe,
input: Child<Input>, input: Child<Input>,
back: Child<Button<&'static str>>, back: Child<Button>,
confirm: Child<Button<&'static str>>, confirm: Child<Button>,
keys: [Child<Button<&'static str>>; KEY_COUNT], keys: [Child<Button>; KEY_COUNT],
scrollbar: ScrollBar, scrollbar: ScrollBar,
fade: Cell<bool>, fade: Cell<bool>,
} }
@ -71,20 +74,20 @@ impl PassphraseKeyboard {
} }
} }
fn key_text(content: &ButtonContent<&'static str>) -> &'static str { fn key_text(content: &ButtonContent) -> TString<'static> {
match content { match content {
ButtonContent::Text(text) => text, ButtonContent::Text(text) => *text,
ButtonContent::Icon(_) => " ", ButtonContent::Icon(_) => " ".into(),
ButtonContent::IconAndText(_) => " ", ButtonContent::IconAndText(_) => " ".into(),
ButtonContent::Empty => "", ButtonContent::Empty => "".into(),
ButtonContent::IconBlend(_, _, _) => "", ButtonContent::IconBlend(_, _, _) => "".into(),
} }
} }
fn key_content(text: &'static str) -> ButtonContent<&'static str> { fn key_content(text: &'static str) -> ButtonContent {
match text { match text {
" " => ButtonContent::Icon(theme::ICON_SPACE), " " => ButtonContent::Icon(theme::ICON_SPACE),
t => ButtonContent::Text(t), t => ButtonContent::Text(t.into()),
} }
} }
@ -274,7 +277,7 @@ impl Component for PassphraseKeyboard {
// character in textbox. If not, let's just append the first character. // character in textbox. If not, let's just append the first character.
let text = Self::key_text(btn.inner().content()); let text = Self::key_text(btn.inner().content());
self.input.mutate(ctx, |ctx, i| { self.input.mutate(ctx, |ctx, i| {
let edit = i.multi_tap.click_key(ctx, key, text); let edit = text.map(|c| i.multi_tap.click_key(ctx, key, c));
i.textbox.apply(ctx, edit); i.textbox.apply(ctx, edit);
}); });
self.after_edit(ctx); self.after_edit(ctx);

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
use crate::{ use crate::{
error::Error, error::Error,
micropython::buffer::StrBuffer, strutil::{self, StringType, TString},
strutil::{self, StringType},
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
@ -23,23 +22,22 @@ pub enum NumberInputDialogMsg {
InfoRequested, InfoRequested,
} }
pub struct NumberInputDialog<T, F> pub struct NumberInputDialog<F>
where where
F: Fn(u32) -> T, F: Fn(u32) -> TString<'static>,
{ {
area: Rect, area: Rect,
description_func: F, description_func: F,
input: Child<NumberInput>, input: Child<NumberInput>,
paragraphs: Child<Paragraphs<Paragraph<T>>>, paragraphs: Child<Paragraphs<Paragraph<'static>>>,
paragraphs_pad: Pad, paragraphs_pad: Pad,
info_button: Child<Button<StrBuffer>>, info_button: Child<Button>,
confirm_button: Child<Button<StrBuffer>>, confirm_button: Child<Button>,
} }
impl<T, F> NumberInputDialog<T, F> impl<F> NumberInputDialog<F>
where where
F: Fn(u32) -> T, F: Fn(u32) -> TString<'static>,
T: StringType,
{ {
pub fn new(min: u32, max: u32, init_value: u32, description_func: F) -> Result<Self, Error> { pub fn new(min: u32, max: u32, init_value: u32, description_func: F) -> Result<Self, Error> {
let text = description_func(init_value); let text = description_func(init_value);
@ -49,8 +47,8 @@ where
input: NumberInput::new(min, max, init_value).into_child(), input: NumberInput::new(min, max, init_value).into_child(),
paragraphs: Paragraphs::new(Paragraph::new(&theme::TEXT_NORMAL, text)).into_child(), paragraphs: Paragraphs::new(Paragraph::new(&theme::TEXT_NORMAL, text)).into_child(),
paragraphs_pad: Pad::with_background(theme::BG), paragraphs_pad: Pad::with_background(theme::BG),
info_button: Button::with_text(TR::buttons__info.try_into()?).into_child(), info_button: Button::with_text(TR::buttons__info.into()).into_child(),
confirm_button: Button::with_text(TR::buttons__continue.try_into()?) confirm_button: Button::with_text(TR::buttons__continue.into())
.styled(theme::button_confirm()) .styled(theme::button_confirm())
.into_child(), .into_child(),
}) })
@ -73,10 +71,9 @@ where
} }
} }
impl<T, F> Component for NumberInputDialog<T, F> impl<F> Component for NumberInputDialog<F>
where where
T: StringType, F: Fn(u32) -> TString<'static>,
F: Fn(u32) -> T,
{ {
type Msg = NumberInputDialogMsg; type Msg = NumberInputDialogMsg;
@ -143,10 +140,9 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T, F> crate::trace::Trace for NumberInputDialog<T, F> impl<F> crate::trace::Trace for NumberInputDialog<F>
where where
T: StringType, F: Fn(u32) -> TString<'static>,
F: Fn(u32) -> T,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("NumberInputDialog"); t.component("NumberInputDialog");
@ -163,8 +159,8 @@ pub enum NumberInputMsg {
pub struct NumberInput { pub struct NumberInput {
area: Rect, area: Rect,
dec: Child<Button<&'static str>>, dec: Child<Button>,
inc: Child<Button<&'static str>>, inc: Child<Button>,
min: u32, min: u32,
max: u32, max: u32,
value: u32, value: u32,
@ -172,10 +168,10 @@ pub struct NumberInput {
impl NumberInput { impl NumberInput {
pub fn new(min: u32, max: u32, value: u32) -> Self { pub fn new(min: u32, max: u32, value: u32) -> Self {
let dec = Button::with_text("-") let dec = Button::with_text("-".into())
.styled(theme::button_counter()) .styled(theme::button_counter())
.into_child(); .into_child();
let inc = Button::with_text("+") let inc = Button::with_text("+".into())
.styled(theme::button_counter()) .styled(theme::button_counter())
.into_child(); .into_child();
let value = value.clamp(min, max); let value = value.clamp(min, max);
@ -226,7 +222,7 @@ impl Component for NumberInput {
let mut buf = [0u8; 10]; let mut buf = [0u8; 10];
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) { if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
let digit_font = Font::DEMIBOLD; let digit_font = Font::DEMIBOLD;
let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET.y; let y_offset = digit_font.text_height() / 2 + Button::BASELINE_OFFSET.y;
display::rect_fill(self.area, theme::BG); display::rect_fill(self.area, theme::BG);
display::text_center( display::text_center(
self.area.center() + Offset::y(y_offset), self.area.center() + Offset::y(y_offset),
@ -245,7 +241,7 @@ impl Component for NumberInput {
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) { if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
let digit_font = Font::DEMIBOLD; let digit_font = Font::DEMIBOLD;
let y_offset = digit_font.text_height() / 2 + Button::<&str>::BASELINE_OFFSET.y; let y_offset = digit_font.text_height() / 2 + Button::BASELINE_OFFSET.y;
shape::Bar::new(self.area).with_bg(theme::BG).render(target); shape::Bar::new(self.area).with_bg(theme::BG).render(target);
shape::Text::new(self.area.center() + Offset::y(y_offset), text) shape::Text::new(self.area.center() + Offset::y(y_offset), text)

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
error::Error, error::Error,
micropython::buffer::StrBuffer, strutil::TString,
time::Instant, time::Instant,
translations::TR, translations::TR,
ui::{ ui::{
@ -22,7 +22,7 @@ use core::cell::Cell;
/// Allows pagination of inner component. Shows scroll bar, confirm & cancel /// Allows pagination of inner component. Shows scroll bar, confirm & cancel
/// buttons. Optionally handles hold-to-confirm with loader. /// buttons. Optionally handles hold-to-confirm with loader.
pub struct ButtonPage<T, U> { pub struct ButtonPage<T> {
/// Inner component. /// Inner component.
content: T, content: T,
/// Cleared when page changes. /// Cleared when page changes.
@ -32,10 +32,10 @@ pub struct ButtonPage<T, U> {
scrollbar: ScrollBar, scrollbar: ScrollBar,
/// Hold-to-confirm mode whenever this is `Some(loader)`. /// Hold-to-confirm mode whenever this is `Some(loader)`.
loader: Option<Loader>, loader: Option<Loader>,
button_cancel: Option<Button<U>>, button_cancel: Option<Button>,
button_confirm: Button<U>, button_confirm: Button,
button_prev: Button<&'static str>, button_prev: Button,
button_next: Button<&'static str>, button_next: Button,
/// Show cancel button instead of back button. /// Show cancel button instead of back button.
cancel_from_any_page: bool, cancel_from_any_page: bool,
/// Whether to pass-through left swipe to parent component. /// Whether to pass-through left swipe to parent component.
@ -46,24 +46,23 @@ pub struct ButtonPage<T, U> {
fade: Cell<Option<u16>>, fade: Cell<Option<u16>>,
} }
impl<T> ButtonPage<T, StrBuffer> impl<T> ButtonPage<T>
where where
T: Paginate, T: Paginate,
T: Component, T: Component,
{ {
pub fn with_hold(mut self) -> Result<Self, Error> { pub fn with_hold(mut self) -> Result<Self, Error> {
self.button_confirm = Button::with_text(TR::buttons__hold_to_confirm.try_into()?) self.button_confirm =
.styled(theme::button_confirm()); Button::with_text(TR::buttons__hold_to_confirm.into()).styled(theme::button_confirm());
self.loader = Some(Loader::new()); self.loader = Some(Loader::new());
Ok(self) Ok(self)
} }
} }
impl<T, U> ButtonPage<T, U> impl<T> ButtonPage<T>
where where
T: Paginate, T: Paginate,
T: Component, T: Component,
U: AsRef<str> + From<&'static str>,
{ {
pub fn new(content: T, background: Color) -> Self { pub fn new(content: T, background: Color) -> Self {
Self { Self {
@ -88,13 +87,17 @@ where
self self
} }
pub fn with_cancel_confirm(mut self, left: Option<U>, right: Option<U>) -> Self { pub fn with_cancel_confirm(
mut self,
left: Option<TString<'static>>,
right: Option<TString<'static>>,
) -> Self {
let cancel = match left { let cancel = match left {
Some(verb) => match verb.as_ref() { Some(verb) => verb.map(|s| match s {
"^" => Button::with_icon(theme::ICON_UP), "^" => Button::with_icon(theme::ICON_UP),
"<" => Button::with_icon(theme::ICON_BACK), "<" => Button::with_icon(theme::ICON_BACK),
_ => Button::with_text(verb), _ => Button::with_text(verb),
}, }),
_ => Button::with_icon(theme::ICON_CANCEL), _ => Button::with_icon(theme::ICON_CANCEL),
}; };
let confirm = match right { let confirm = match right {
@ -285,11 +288,10 @@ enum HandleResult<T> {
Continue, Continue,
} }
impl<T, U> Component for ButtonPage<T, U> impl<T> Component for ButtonPage<T>
where where
T: Paginate, T: Paginate,
T: Component, T: Component,
U: AsRef<str> + From<&'static str>,
{ {
type Msg = PageMsg<T::Msg>; type Msg = PageMsg<T::Msg>;
@ -297,7 +299,7 @@ where
let small_left_button = match (&self.button_cancel, &self.button_confirm) { let small_left_button = match (&self.button_cancel, &self.button_confirm) {
(None, _) => true, (None, _) => true,
(Some(cancel), confirm) => match (cancel.content(), confirm.content()) { (Some(cancel), confirm) => match (cancel.content(), confirm.content()) {
(ButtonContent::Text(t), _) => t.as_ref().len() <= 4, (ButtonContent::Text(t), _) => t.len() <= 4,
(ButtonContent::Icon(_), ButtonContent::Icon(_)) => false, (ButtonContent::Icon(_), ButtonContent::Icon(_)) => false,
_ => true, _ => true,
}, },
@ -455,7 +457,7 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for ButtonPage<T, U> impl<T> crate::trace::Trace for ButtonPage<T>
where where
T: crate::trace::Trace, T: crate::trace::Trace,
{ {
@ -532,12 +534,6 @@ mod tests {
const SCREEN: Rect = constant::screen().inset(theme::borders()); const SCREEN: Rect = constant::screen().inset(theme::borders());
impl SkipPrefix for &str {
fn skip_prefix(&self, chars: usize) -> Self {
&self[chars..]
}
}
fn swipe(component: &mut impl Component, points: &[(i16, i16)]) { fn swipe(component: &mut impl Component, points: &[(i16, i16)]) {
let last = points.len().saturating_sub(1); let last = points.len().saturating_sub(1);
let mut first = true; let mut first = true;
@ -567,10 +563,7 @@ mod tests {
#[test] #[test]
fn paragraphs_empty() { fn paragraphs_empty() {
let mut page = ButtonPage::<_, &'static str>::new( let mut page = ButtonPage::new(Paragraphs::<[Paragraph<'static>; 0]>::new([]), theme::BG);
Paragraphs::<[Paragraph<&'static str>; 0]>::new([]),
theme::BG,
);
page.place(SCREEN); page.place(SCREEN);
let expected = serde_json::json!({ let expected = serde_json::json!({
@ -593,7 +586,7 @@ mod tests {
#[test] #[test]
fn paragraphs_single() { fn paragraphs_single() {
let mut page = ButtonPage::<_, &'static str>::new( let mut page = ButtonPage::new(
Paragraphs::new([ Paragraphs::new([
Paragraph::new( Paragraph::new(
&theme::TEXT_NORMAL, &theme::TEXT_NORMAL,
@ -631,7 +624,7 @@ mod tests {
#[test] #[test]
fn paragraphs_one_long() { fn paragraphs_one_long() {
let mut page = ButtonPage::<_, &'static str>::new( let mut page = ButtonPage::new(
Paragraphs::new( Paragraphs::new(
Paragraph::new( Paragraph::new(
&theme::TEXT_BOLD, &theme::TEXT_BOLD,
@ -689,7 +682,7 @@ mod tests {
#[test] #[test]
fn paragraphs_three_long() { fn paragraphs_three_long() {
let mut page = ButtonPage::<_, &'static str>::new( let mut page = ButtonPage::new(
Paragraphs::new([ Paragraphs::new([
Paragraph::new( Paragraph::new(
&theme::TEXT_BOLD, &theme::TEXT_BOLD,
@ -789,7 +782,7 @@ mod tests {
#[test] #[test]
fn paragraphs_hard_break() { fn paragraphs_hard_break() {
let mut page = ButtonPage::<_, &'static str>::new( let mut page = ButtonPage::new(
Paragraphs::new([ Paragraphs::new([
Paragraph::new(&theme::TEXT_NORMAL, "Short one.").break_after(), Paragraph::new(&theme::TEXT_NORMAL, "Short one.").break_after(),
Paragraph::new(&theme::TEXT_NORMAL, "Short two.").break_after(), Paragraph::new(&theme::TEXT_NORMAL, "Short two.").break_after(),

View File

@ -1,8 +1,7 @@
use core::mem; use core::mem;
use crate::{ use crate::{
error::Error, strutil::TString,
strutil::StringType,
ui::{ ui::{
component::{ component::{
base::ComponentExt, base::ComponentExt,
@ -21,27 +20,22 @@ use crate::{
use super::theme; use super::theme;
pub struct Progress<T> { pub struct Progress {
title: Child<Label<T>>, title: Child<Label<'static>>,
value: u16, value: u16,
loader_y_offset: i16, loader_y_offset: i16,
indeterminate: bool, indeterminate: bool,
description: Child<Paragraphs<Paragraph<T>>>, description: Child<Paragraphs<Paragraph<'static>>>,
description_pad: Pad, description_pad: Pad,
update_description: fn(&str) -> Result<T, Error>,
} }
impl<T> Progress<T> impl Progress {
where
T: StringType,
{
const AREA: Rect = constant::screen().inset(theme::borders()); const AREA: Rect = constant::screen().inset(theme::borders());
pub fn new( pub fn new(
title: T, title: TString<'static>,
indeterminate: bool, indeterminate: bool,
description: T, description: TString<'static>,
update_description: fn(&str) -> Result<T, Error>,
) -> Self { ) -> Self {
Self { Self {
title: Label::centered(title, theme::label_progress()).into_child(), title: Label::centered(title, theme::label_progress()).into_child(),
@ -53,15 +47,11 @@ where
) )
.into_child(), .into_child(),
description_pad: Pad::with_background(theme::BG), description_pad: Pad::with_background(theme::BG),
update_description,
} }
} }
} }
impl<T> Component for Progress<T> impl Component for Progress {
where
T: StringType,
{
type Msg = Never; type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect { fn place(&mut self, _bounds: Rect) -> Rect {
@ -70,10 +60,7 @@ where
.inner() .inner()
.inner() .inner()
.content() .content()
.as_ref() .map(|t| t.chars().filter(|c| *c == '\n').count() as i16);
.chars()
.filter(|c| *c == '\n')
.count() as i16;
let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y); let (title, rest) = Self::AREA.split_top(self.title.inner().max_size().y);
let (loader, description) = let (loader, description) =
rest.split_bottom(Font::NORMAL.line_height() * description_lines); rest.split_bottom(Font::NORMAL.line_height() * description_lines);
@ -92,8 +79,7 @@ where
ctx.request_paint(); ctx.request_paint();
} }
self.description.mutate(ctx, |ctx, para| { self.description.mutate(ctx, |ctx, para| {
if para.inner_mut().content().as_ref() != new_description { if para.inner_mut().content() != &new_description {
let new_description = unwrap!((self.update_description)(new_description));
para.inner_mut().update(new_description); para.inner_mut().update(new_description);
para.change_page(0); // Recompute bounding box. para.change_page(0); // Recompute bounding box.
ctx.request_paint(); ctx.request_paint();
@ -172,10 +158,7 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Progress<T> impl crate::trace::Trace for Progress {
where
T: StringType,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Progress"); t.component("Progress");
} }

View File

@ -1,12 +1,16 @@
use heapless::Vec; use heapless::Vec;
use super::theme; use super::theme;
use crate::ui::{ use crate::{
micropython::buffer::StrBuffer,
strutil::TString,
ui::{
component::{base::Component, Event, EventCtx}, component::{base::Component, Event, EventCtx},
display::Icon, display::Icon,
geometry::Rect, geometry::Rect,
model_mercury::component::button::{Button, ButtonMsg, IconText}, model_mercury::component::button::{Button, ButtonMsg, IconText},
shape::{Bar, Renderer}, shape::{Bar, Renderer},
},
}; };
pub enum VerticalMenuChoiceMsg { pub enum VerticalMenuChoiceMsg {
@ -27,39 +31,37 @@ const MENU_BUTTON_HEIGHT: i16 = 64;
/// Fixed height of a separator. /// Fixed height of a separator.
const MENU_SEP_HEIGHT: i16 = 2; const MENU_SEP_HEIGHT: i16 = 2;
type VerticalMenuButtons<T> = Vec<Button<T>, N_ITEMS>; type VerticalMenuButtons = Vec<Button, N_ITEMS>;
type AreasForSeparators = Vec<Rect, N_SEPS>; type AreasForSeparators = Vec<Rect, N_SEPS>;
pub struct VerticalMenu<T> { pub struct VerticalMenu {
area: Rect, area: Rect,
/// buttons placed vertically from top to bottom /// buttons placed vertically from top to bottom
buttons: VerticalMenuButtons<T>, buttons: VerticalMenuButtons,
/// areas for visual separators between buttons /// areas for visual separators between buttons
areas_sep: AreasForSeparators, areas_sep: AreasForSeparators,
} }
impl<T> VerticalMenu<T> impl VerticalMenu {
where fn new(buttons: VerticalMenuButtons) -> Self {
T: AsRef<str>,
{
fn new(buttons: VerticalMenuButtons<T>) -> Self {
Self { Self {
area: Rect::zero(), area: Rect::zero(),
buttons, buttons,
areas_sep: AreasForSeparators::new(), areas_sep: AreasForSeparators::new(),
} }
} }
pub fn select_word(words: [T; 3]) -> Self { pub fn select_word(words: [StrBuffer; 3]) -> Self {
let mut buttons_vec = VerticalMenuButtons::new(); let mut buttons_vec = VerticalMenuButtons::new();
for word in words { for word in words {
let button = Button::with_text(word).styled(theme::button_vertical_menu()); let button = Button::with_text(word.into()).styled(theme::button_vertical_menu());
unwrap!(buttons_vec.push(button)); unwrap!(buttons_vec.push(button));
} }
Self::new(buttons_vec) Self::new(buttons_vec)
} }
pub fn context_menu(options: [(T, Icon); 3]) -> Self { pub fn context_menu(options: [(&'static str, Icon); 3]) -> Self {
// TODO: this is just POC // TODO: this is just POC
// FIXME: args should be TString when IconText has TString
let mut buttons_vec = VerticalMenuButtons::new(); let mut buttons_vec = VerticalMenuButtons::new();
for opt in options { for opt in options {
let button_theme; let button_theme;
@ -79,10 +81,7 @@ where
} }
} }
impl<T> Component for VerticalMenu<T> impl Component for VerticalMenu {
where
T: AsRef<str>,
{
type Msg = VerticalMenuChoiceMsg; type Msg = VerticalMenuChoiceMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -141,10 +140,7 @@ where
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for VerticalMenu<T> impl crate::trace::Trace for VerticalMenu {
where
T: AsRef<str>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("VerticalMenu"); t.component("VerticalMenu");
t.in_list("buttons", &|button_list| { t.in_list("buttons", &|button_list| {

File diff suppressed because it is too large Load Diff