fix(core): rebase on drawlib and TStringificaton

[no changelog]
obrusvit/ui-t3t1-reset-device-apple-hotfix
obrusvit 1 month ago
parent a801b00dc2
commit 2c89fc469e

@ -9,5 +9,5 @@
pub use super::model_mercury::constant::*;
#[cfg(all(feature = "model_tr", not(feature = "model_tt")))]
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::*;

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

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

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

@ -1,5 +1,5 @@
use crate::{
strutil::StringType,
strutil::TString,
ui::{
component::{
image::BlendedImage,
@ -97,18 +97,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(
@ -125,26 +124,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(
@ -171,9 +170,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>;
@ -219,9 +217,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) {

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

@ -3,6 +3,9 @@
//! do not edit manually!
use crate::strutil::TString;
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_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.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// 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 {
match icon_name.as_ref() {
icon_name.map(|c| match c {
"apple" => ICON_APPLE,
"aws" => ICON_AWS,
"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,
"tutanota" => ICON_TUTANOTA,
_ => ICON_WEBAUTHN,
}
})
} else {
ICON_WEBAUTHN
}

@ -2,6 +2,9 @@
//! (by running `make templates` in `core`)
//! do not edit manually!
use crate::strutil::TString;
<%
icons: list[tuple[str, str]] = []
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.
/// Returns default `ICON_WEBAUTHN` when the icon is not found or name not
/// 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 {
match icon_name.as_ref() {
icon_name.map(|c| match c {
% for icon_name, var_name in icons:
"${icon_name}" => ICON_${var_name},
% endfor
_ => ICON_WEBAUTHN,
}
})
} else {
ICON_WEBAUTHN
}

@ -62,7 +62,7 @@ where
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;
self.title = Child::new(self.title.into_inner().top_aligned());
self.subtitle = Some(Child::new(Label::new(

@ -288,6 +288,13 @@ impl Component for Homescreen {
}
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 pos = Point::new(self.pad.area.center().x, LABEL_Y);
shape::Text::new(pos, t)
@ -355,15 +362,15 @@ impl crate::trace::Trace for Homescreen {
}
}
pub struct Lockscreen {
label: TString<'static>,
pub struct Lockscreen<'a> {
label: TString<'a>,
custom_image: Option<Gc<[u8]>>,
bootscreen: bool,
coinjoin_authorized: bool,
}
impl Lockscreen {
pub fn new(label: TString<'static>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
impl<'a> Lockscreen<'a> {
pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
Lockscreen {
label,
custom_image: get_user_custom_image().ok(),
@ -373,7 +380,7 @@ impl Lockscreen {
}
}
impl Component for Lockscreen {
impl Component for Lockscreen<'_> {
type Msg = HomescreenMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@ -470,7 +477,7 @@ impl Component for Lockscreen {
shape::JpegImage::new(center, img_data)
.with_align(Alignment2D::CENTER)
.with_blur(4)
.with_dim(130)
.with_dim(140)
.render(target);
} else if is_image_toif(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")]
impl crate::trace::Trace for Lockscreen {
impl crate::trace::Trace for Lockscreen<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Lockscreen");
}

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

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

@ -1,18 +1,21 @@
use crate::ui::{
component::{
base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Never,
use crate::{
strutil::TString,
ui::{
component::{
base::ComponentExt, text::common::TextBox, Child, Component, Event, EventCtx, Never,
},
display,
geometry::{Grid, Offset, Rect},
model_mercury::component::{
button::{Button, ButtonContent, ButtonMsg},
keyboard::common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard},
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
shape,
shape::Renderer,
util::long_line_content_with_ellipsis,
},
display,
geometry::{Grid, Offset, Rect},
model_mercury::component::{
button::{Button, ButtonContent, ButtonMsg},
keyboard::common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard},
swipe::{Swipe, SwipeDirection},
theme, ScrollBar,
},
shape,
shape::Renderer,
util::long_line_content_with_ellipsis,
};
use core::cell::Cell;
@ -25,9 +28,9 @@ pub enum PassphraseKeyboardMsg {
pub struct PassphraseKeyboard {
page_swipe: Swipe,
input: Child<Input>,
back: Child<Button<&'static str>>,
confirm: Child<Button<&'static str>>,
keys: [Child<Button<&'static str>>; KEY_COUNT],
back: Child<Button>,
confirm: Child<Button>,
keys: [Child<Button>; KEY_COUNT],
scrollbar: ScrollBar,
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 {
ButtonContent::Text(text) => text,
ButtonContent::Icon(_) => " ",
ButtonContent::IconAndText(_) => " ",
ButtonContent::Empty => "",
ButtonContent::IconBlend(_, _, _) => "",
ButtonContent::Text(text) => *text,
ButtonContent::Icon(_) => " ".into(),
ButtonContent::IconAndText(_) => " ".into(),
ButtonContent::Empty => "".into(),
ButtonContent::IconBlend(_, _, _) => "".into(),
}
}
fn key_content(text: &'static str) -> ButtonContent<&'static str> {
fn key_content(text: &'static str) -> ButtonContent {
match text {
" " => 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.
let text = Self::key_text(btn.inner().content());
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);
});
self.after_edit(ctx);

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

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

@ -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)];
pub struct SelectWordCount {
button: [Button<&'static str>; NUMBERS.len()],
button: [Button; NUMBERS.len()],
}
pub enum SelectWordCountMsg {
@ -23,7 +23,7 @@ pub enum SelectWordCountMsg {
impl SelectWordCount {
pub fn new() -> Self {
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())),
}
}
}

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

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

@ -1,8 +1,7 @@
use core::mem;
use crate::{
error::Error,
strutil::StringType,
strutil::TString,
ui::{
component::{
base::ComponentExt,
@ -21,27 +20,22 @@ use crate::{
use super::theme;
pub struct Progress<T> {
title: Child<Label<T>>,
pub struct Progress {
title: Child<Label<'static>>,
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>
where
T: StringType,
{
impl Progress {
const AREA: Rect = constant::screen().inset(theme::borders());
pub fn new(
title: T,
title: TString<'static>,
indeterminate: bool,
description: T,
update_description: fn(&str) -> Result<T, Error>,
description: TString<'static>,
) -> Self {
Self {
title: Label::centered(title, theme::label_progress()).into_child(),
@ -53,15 +47,11 @@ where
)
.into_child(),
description_pad: Pad::with_background(theme::BG),
update_description,
}
}
}
impl<T> Component for Progress<T>
where
T: StringType,
{
impl Component for Progress {
type Msg = Never;
fn place(&mut self, _bounds: Rect) -> Rect {
@ -70,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);
@ -92,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();
@ -172,10 +158,7 @@ where
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for Progress<T>
where
T: StringType,
{
impl crate::trace::Trace for Progress {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Progress");
}

@ -96,7 +96,6 @@ impl Component for ResultFooter<'_> {
// footer text
self.text.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
// divider line
let bar = Rect::from_center_and_size(

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

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save