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:
parent
8978f36096
commit
ac39b026cf
@ -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");
|
||||
}
|
||||
|
@ -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…
Reference in New Issue
Block a user