mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-08 07:38:11 +00:00
WIP - implementing QR code
This commit is contained in:
parent
6e9e7265c4
commit
dd79a868a3
@ -373,7 +373,7 @@ pub struct TextRenderer;
|
|||||||
|
|
||||||
impl LayoutSink for TextRenderer {
|
impl LayoutSink for TextRenderer {
|
||||||
fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str) {
|
fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str) {
|
||||||
display::text(
|
display::text_left(
|
||||||
cursor,
|
cursor,
|
||||||
text,
|
text,
|
||||||
layout.style.text_font,
|
layout.style.text_font,
|
||||||
@ -383,7 +383,7 @@ impl LayoutSink for TextRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hyphen(&mut self, cursor: Point, layout: &TextLayout) {
|
fn hyphen(&mut self, cursor: Point, layout: &TextLayout) {
|
||||||
display::text(
|
display::text_left(
|
||||||
cursor,
|
cursor,
|
||||||
"-",
|
"-",
|
||||||
layout.style.text_font,
|
layout.style.text_font,
|
||||||
@ -403,7 +403,7 @@ impl LayoutSink for TextRenderer {
|
|||||||
layout.style.background_color,
|
layout.style.background_color,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
display::text(
|
display::text_left(
|
||||||
cursor,
|
cursor,
|
||||||
"...",
|
"...",
|
||||||
layout.style.text_font,
|
layout.style.text_font,
|
||||||
|
@ -6,6 +6,8 @@ pub mod loader;
|
|||||||
#[cfg(feature = "jpeg")]
|
#[cfg(feature = "jpeg")]
|
||||||
pub mod tjpgd;
|
pub mod tjpgd;
|
||||||
|
|
||||||
|
use heapless::String;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
constant,
|
constant,
|
||||||
geometry::{Offset, Point, Rect},
|
geometry::{Offset, Point, Rect},
|
||||||
@ -948,11 +950,62 @@ pub fn paint_point(point: &Point, color: Color) {
|
|||||||
display::bar(point.x, point.y, 1, 1, color.into());
|
display::bar(point.x, point.y, 1, 1, color.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display QR code
|
||||||
pub fn qrcode(center: Point, data: &str, max_size: u32, case_sensitive: bool) -> Result<(), Error> {
|
pub fn qrcode(center: Point, data: &str, max_size: u32, case_sensitive: bool) -> Result<(), Error> {
|
||||||
qr::render_qrcode(center.x, center.y, data, max_size, case_sensitive)
|
qr::render_qrcode(center.x, center.y, data, max_size, case_sensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
/// Draws longer multiline texts inside an area.
|
||||||
|
/// Does not add any characters on the line boundaries.
|
||||||
|
///
|
||||||
|
/// If it fits, returns the rest of the area.
|
||||||
|
/// If it does not fit, returns `None`.
|
||||||
|
pub fn text_multiline(
|
||||||
|
area: Rect,
|
||||||
|
text: &str,
|
||||||
|
font: Font,
|
||||||
|
fg_color: Color,
|
||||||
|
bg_color: Color,
|
||||||
|
) -> Option<Rect> {
|
||||||
|
let line_height = font.line_height();
|
||||||
|
let characters_overall = text.chars().count();
|
||||||
|
let mut taken_from_top = 0;
|
||||||
|
let mut characters_drawn = 0;
|
||||||
|
'lines: loop {
|
||||||
|
let baseline = area.top_left() + Offset::y(line_height + taken_from_top);
|
||||||
|
if !area.contains(baseline) {
|
||||||
|
// The whole area was consumed.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut line_text: String<50> = String::new();
|
||||||
|
'characters: loop {
|
||||||
|
if let Some(character) = text.chars().nth(characters_drawn) {
|
||||||
|
unwrap!(line_text.push(character));
|
||||||
|
characters_drawn += 1;
|
||||||
|
} else {
|
||||||
|
// No more characters to draw.
|
||||||
|
break 'characters;
|
||||||
|
}
|
||||||
|
if font.text_width(&line_text) > area.width() {
|
||||||
|
// Cannot fit on the line anymore.
|
||||||
|
line_text.pop();
|
||||||
|
characters_drawn -= 1;
|
||||||
|
break 'characters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text_left(baseline, &line_text, font, fg_color, bg_color);
|
||||||
|
if characters_drawn == characters_overall {
|
||||||
|
// No more lines to draw.
|
||||||
|
break 'lines;
|
||||||
|
}
|
||||||
|
taken_from_top += line_height;
|
||||||
|
}
|
||||||
|
// Some of the area was unused and is free to draw some further text.
|
||||||
|
Some(area.split_top(taken_from_top).1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display text left-alligned to a certain Point
|
||||||
|
pub fn text_left(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
||||||
display::text(
|
display::text(
|
||||||
baseline.x,
|
baseline.x,
|
||||||
baseline.y,
|
baseline.y,
|
||||||
@ -963,6 +1016,7 @@ pub fn text(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display text centered around a certain Point
|
||||||
pub fn text_center(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
pub fn text_center(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
||||||
let w = font.text_width(text);
|
let w = font.text_width(text);
|
||||||
display::text(
|
display::text(
|
||||||
@ -975,6 +1029,7 @@ pub fn text_center(baseline: Point, text: &str, font: Font, fg_color: Color, bg_
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Display text right-alligned to a certain Point
|
||||||
pub fn text_right(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
pub fn text_right(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
||||||
let w = font.text_width(text);
|
let w = font.text_width(text);
|
||||||
display::text(
|
display::text(
|
||||||
|
@ -137,7 +137,7 @@ where
|
|||||||
display::rect_fill(self.area, background_color)
|
display::rect_fill(self.area, background_color)
|
||||||
}
|
}
|
||||||
|
|
||||||
display::text(
|
display::text_left(
|
||||||
self.baseline,
|
self.baseline,
|
||||||
text.as_ref(),
|
text.as_ref(),
|
||||||
style.font,
|
style.font,
|
||||||
|
@ -52,7 +52,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
display::text(
|
display::text_left(
|
||||||
self.area.bottom_left() - Offset::y(2),
|
self.area.bottom_left() - Offset::y(2),
|
||||||
self.title.as_ref(),
|
self.title.as_ref(),
|
||||||
Font::BOLD,
|
Font::BOLD,
|
||||||
|
@ -249,7 +249,7 @@ where
|
|||||||
|
|
||||||
match &self.content {
|
match &self.content {
|
||||||
ButtonContent::Text(text) => {
|
ButtonContent::Text(text) => {
|
||||||
display::text(
|
display::text_left(
|
||||||
self.get_text_baseline(&style),
|
self.get_text_baseline(&style),
|
||||||
text.as_ref(),
|
text.as_ref(),
|
||||||
style.font,
|
style.font,
|
||||||
|
@ -9,12 +9,12 @@ use super::theme;
|
|||||||
|
|
||||||
/// Display white text on black background
|
/// Display white text on black background
|
||||||
pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
pub fn display<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||||
display::text(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
display::text_left(baseline, text.as_ref(), font, theme::FG, theme::BG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display black text on white background
|
/// Display black text on white background
|
||||||
pub fn display_inverse<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
pub fn display_inverse<T: AsRef<str>>(baseline: Point, text: &T, font: Font) {
|
||||||
display::text(baseline, text.as_ref(), font, theme::BG, theme::FG);
|
display::text_left(baseline, text.as_ref(), font, theme::BG, theme::FG);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display white text on black background,
|
/// Display white text on black background,
|
||||||
|
@ -500,7 +500,7 @@ impl LayoutSink for TextRenderer {
|
|||||||
|
|
||||||
match layout.style.line_alignment {
|
match layout.style.line_alignment {
|
||||||
LineAlignment::Left => {
|
LineAlignment::Left => {
|
||||||
display::text(
|
display::text_left(
|
||||||
cursor,
|
cursor,
|
||||||
text,
|
text,
|
||||||
layout.style.text_font,
|
layout.style.text_font,
|
||||||
@ -532,7 +532,7 @@ impl LayoutSink for TextRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hyphen(&mut self, cursor: Point, layout: &TextLayout) {
|
fn hyphen(&mut self, cursor: Point, layout: &TextLayout) {
|
||||||
display::text(
|
display::text_left(
|
||||||
cursor,
|
cursor,
|
||||||
"-",
|
"-",
|
||||||
layout.style.text_font,
|
layout.style.text_font,
|
||||||
@ -552,7 +552,7 @@ impl LayoutSink for TextRenderer {
|
|||||||
layout.style.background_color,
|
layout.style.background_color,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
display::text(
|
display::text_left(
|
||||||
cursor,
|
cursor,
|
||||||
"...",
|
"...",
|
||||||
layout.style.text_font,
|
layout.style.text_font,
|
||||||
|
@ -15,6 +15,7 @@ mod loader;
|
|||||||
mod page;
|
mod page;
|
||||||
mod passphrase;
|
mod passphrase;
|
||||||
mod pin;
|
mod pin;
|
||||||
|
mod qr_code;
|
||||||
mod result_anim;
|
mod result_anim;
|
||||||
mod result_popup;
|
mod result_popup;
|
||||||
mod scrollbar;
|
mod scrollbar;
|
||||||
@ -43,6 +44,7 @@ pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
|||||||
pub use page::ButtonPage;
|
pub use page::ButtonPage;
|
||||||
pub use passphrase::{PassphraseEntry, PassphraseEntryMsg};
|
pub use passphrase::{PassphraseEntry, PassphraseEntryMsg};
|
||||||
pub use pin::{PinEntry, PinEntryMsg};
|
pub use pin::{PinEntry, PinEntryMsg};
|
||||||
|
pub use qr_code::{QRCodePage, QRCodePageMessage};
|
||||||
pub use result_anim::{ResultAnim, ResultAnimMsg};
|
pub use result_anim::{ResultAnim, ResultAnimMsg};
|
||||||
pub use result_popup::{ResultPopup, ResultPopupMsg};
|
pub use result_popup::{ResultPopup, ResultPopupMsg};
|
||||||
pub use scrollbar::ScrollBar;
|
pub use scrollbar::ScrollBar;
|
||||||
|
113
core/embed/rust/src/ui/model_tr/component/qr_code.rs
Normal file
113
core/embed/rust/src/ui/model_tr/component/qr_code.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Child, Component, Event, EventCtx},
|
||||||
|
display::{self, Font},
|
||||||
|
geometry::{Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos};
|
||||||
|
|
||||||
|
pub enum QRCodePageMessage {
|
||||||
|
Confirmed,
|
||||||
|
Cancelled,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QRCodePage<F, T> {
|
||||||
|
title: T,
|
||||||
|
title_area: Rect,
|
||||||
|
qr_code: F,
|
||||||
|
buttons: Child<ButtonController<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, T> QRCodePage<F, T>
|
||||||
|
where
|
||||||
|
T: AsRef<str> + Clone,
|
||||||
|
{
|
||||||
|
pub fn new(title: T, qr_code: F, btn_layout: ButtonLayout<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
title,
|
||||||
|
title_area: Rect::zero(),
|
||||||
|
qr_code,
|
||||||
|
buttons: Child::new(ButtonController::new(btn_layout)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, T> Component for QRCodePage<F, T>
|
||||||
|
where
|
||||||
|
T: AsRef<str> + Clone,
|
||||||
|
F: Component,
|
||||||
|
{
|
||||||
|
type Msg = QRCodePageMessage;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
|
||||||
|
let (qr_code_area, title_area) = content_area.split_left(theme::QR_SIDE_MAX);
|
||||||
|
self.title_area = title_area;
|
||||||
|
self.qr_code.place(qr_code_area);
|
||||||
|
self.buttons.place(button_area);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
let button_event = self.buttons.event(ctx, event);
|
||||||
|
|
||||||
|
if let Some(ButtonControllerMsg::Triggered(pos)) = button_event {
|
||||||
|
match pos {
|
||||||
|
ButtonPos::Left => {
|
||||||
|
return Some(QRCodePageMessage::Cancelled);
|
||||||
|
}
|
||||||
|
ButtonPos::Right => {
|
||||||
|
return Some(QRCodePageMessage::Confirmed);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.qr_code.paint();
|
||||||
|
// TODO: add the Label from Suite
|
||||||
|
display::text_multiline(
|
||||||
|
self.title_area,
|
||||||
|
self.title.as_ref(),
|
||||||
|
Font::MONO,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
);
|
||||||
|
self.buttons.paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
use super::ButtonAction;
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
use heapless::String;
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<F, T> crate::trace::Trace for QRCodePage<F, T>
|
||||||
|
where
|
||||||
|
T: AsRef<str> + Clone,
|
||||||
|
{
|
||||||
|
fn get_btn_action(&self, pos: ButtonPos) -> String<25> {
|
||||||
|
match pos {
|
||||||
|
ButtonPos::Left => ButtonAction::Cancel.string(),
|
||||||
|
ButtonPos::Right => ButtonAction::Confirm.string(),
|
||||||
|
ButtonPos::Middle => ButtonAction::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("QRCodePage");
|
||||||
|
t.kw_pair("active_page", "0");
|
||||||
|
t.kw_pair("page_count", "1");
|
||||||
|
self.report_btn_actions(t);
|
||||||
|
t.content_flag();
|
||||||
|
t.string("QR CODE");
|
||||||
|
t.string(self.title.as_ref());
|
||||||
|
t.content_flag();
|
||||||
|
t.field("buttons", &self.buttons);
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
base::{Component, ComponentExt},
|
base::{Component, ComponentExt},
|
||||||
paginated::{PageMsg, Paginate},
|
paginated::{PageMsg, Paginate},
|
||||||
|
painter,
|
||||||
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecLong, Paragraphs, VecExt},
|
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecLong, Paragraphs, VecExt},
|
||||||
FormattedText,
|
FormattedText,
|
||||||
},
|
},
|
||||||
@ -35,7 +36,7 @@ use super::{
|
|||||||
component::{
|
component::{
|
||||||
Bip39Entry, Bip39EntryMsg, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, Flow,
|
Bip39Entry, Bip39EntryMsg, ButtonActions, ButtonDetails, ButtonLayout, ButtonPage, Flow,
|
||||||
FlowMsg, FlowPages, Frame, Page, PassphraseEntry, PassphraseEntryMsg, PinEntry,
|
FlowMsg, FlowPages, Frame, Page, PassphraseEntry, PassphraseEntryMsg, PinEntry,
|
||||||
PinEntryMsg, ShareWords, SimpleChoice, SimpleChoiceMsg,
|
PinEntryMsg, QRCodePage, QRCodePageMessage, ShareWords, SimpleChoice, SimpleChoiceMsg,
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
@ -68,6 +69,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F, T> ComponentMsgObj for QRCodePage<F, T>
|
||||||
|
where
|
||||||
|
T: AsRef<str> + Clone,
|
||||||
|
F: Component,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
QRCodePageMessage::Confirmed => Ok(CONFIRMED.as_obj()),
|
||||||
|
QRCodePageMessage::Cancelled => Ok(CANCELLED.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ComponentMsgObj for PinEntry {
|
impl ComponentMsgObj for PinEntry {
|
||||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
match msg {
|
match msg {
|
||||||
@ -132,22 +146,26 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
|||||||
// TODO: could be replaced by Flow with one element after it supports pagination
|
// TODO: could be replaced by Flow with one element after it supports pagination
|
||||||
|
|
||||||
let format = match (&action, &description, reverse) {
|
let format = match (&action, &description, reverse) {
|
||||||
(Some(_), Some(_), false) => "{Font::bold}{action}\n\r{Font::normal}{description}",
|
(Some(_), Some(_), false) => "{bold}{action}\n\r{mono}{description}",
|
||||||
(Some(_), Some(_), true) => "{Font::normal}{description}\n\r{Font::bold}{action}",
|
(Some(_), Some(_), true) => "{mono}{description}\n\r{bold}{action}",
|
||||||
(Some(_), None, _) => "{Font::bold}{action}",
|
(Some(_), None, _) => "{bold}{action}",
|
||||||
(None, Some(_), _) => "{Font::normal}{description}",
|
(None, Some(_), _) => "{mono}{description}",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let verb_cancel = verb_cancel.unwrap_or_default();
|
// Left button - icon, text or nothing.
|
||||||
let verb = verb.unwrap_or_default();
|
let cancel_btn = if let Some(verb_cancel) = verb_cancel {
|
||||||
|
if verb_cancel.len() > 0 {
|
||||||
let cancel_btn = if verb_cancel.len() > 0 {
|
Some(ButtonDetails::text(verb_cancel))
|
||||||
|
} else {
|
||||||
Some(ButtonDetails::cancel_icon())
|
Some(ButtonDetails::cancel_icon())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Right button - text or nothing.
|
||||||
|
let verb = verb.unwrap_or_default();
|
||||||
let mut confirm_btn = if verb.len() > 0 {
|
let mut confirm_btn = if verb.len() > 0 {
|
||||||
Some(ButtonDetails::text(verb))
|
Some(ButtonDetails::text(verb))
|
||||||
} else {
|
} else {
|
||||||
@ -350,6 +368,29 @@ extern "C" fn confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn show_qr(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
|
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
|
||||||
|
let verb_cancel: StrBuffer = kwargs.get(Qstr::MP_QSTR_verb_cancel)?.try_into()?;
|
||||||
|
let case_sensitive: bool = kwargs.get(Qstr::MP_QSTR_case_sensitive)?.try_into()?;
|
||||||
|
|
||||||
|
let verb: StrBuffer = "CONFIRM".into();
|
||||||
|
|
||||||
|
let qr_code = painter::qrcode_painter(address, theme::QR_SIDE_MAX as u32, case_sensitive);
|
||||||
|
|
||||||
|
let btn_layout = ButtonLayout::new(
|
||||||
|
Some(ButtonDetails::text(verb_cancel)),
|
||||||
|
None,
|
||||||
|
Some(ButtonDetails::text(verb)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(QRCodePage::new(title, qr_code, btn_layout))?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
/// General pattern of most tutorial screens.
|
/// General pattern of most tutorial screens.
|
||||||
/// (title, text, btn_layout, btn_actions)
|
/// (title, text, btn_layout, btn_actions)
|
||||||
fn tutorial_screen(
|
fn tutorial_screen(
|
||||||
@ -611,6 +652,16 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// """Confirm summary of a transaction. Specific for model R."""
|
/// """Confirm summary of a transaction. Specific for model R."""
|
||||||
Qstr::MP_QSTR_confirm_total_r => obj_fn_kw!(0, confirm_total).as_obj(),
|
Qstr::MP_QSTR_confirm_total_r => obj_fn_kw!(0, confirm_total).as_obj(),
|
||||||
|
|
||||||
|
/// def show_qr(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// address: str,
|
||||||
|
/// verb_cancel: str,
|
||||||
|
/// case_sensitive: bool,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Show QR code."""
|
||||||
|
Qstr::MP_QSTR_show_qr => obj_fn_kw!(0, show_qr).as_obj(),
|
||||||
|
|
||||||
/// def tutorial() -> object:
|
/// def tutorial() -> object:
|
||||||
/// """Show user how to interact with the device."""
|
/// """Show user how to interact with the device."""
|
||||||
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(),
|
Qstr::MP_QSTR_tutorial => obj_fn_kw!(0, tutorial).as_obj(),
|
||||||
|
@ -80,3 +80,6 @@ pub const ICON_WARNING: IconAndName =
|
|||||||
pub const BUTTON_CONTENT_HEIGHT: i16 = 7;
|
pub const BUTTON_CONTENT_HEIGHT: i16 = 7;
|
||||||
pub const BUTTON_OUTLINE: i16 = 3;
|
pub const BUTTON_OUTLINE: i16 = 3;
|
||||||
pub const BUTTON_HEIGHT: i16 = BUTTON_CONTENT_HEIGHT + 2 * BUTTON_OUTLINE;
|
pub const BUTTON_HEIGHT: i16 = BUTTON_CONTENT_HEIGHT + 2 * BUTTON_OUTLINE;
|
||||||
|
|
||||||
|
// Full-size QR code.
|
||||||
|
pub const QR_SIDE_MAX: i16 = 64 - BUTTON_HEIGHT;
|
||||||
|
@ -189,7 +189,7 @@ impl<T> Button<T> {
|
|||||||
let start_of_baseline = self.area.center()
|
let start_of_baseline = self.area.center()
|
||||||
+ Offset::new(-width / 2, height / 2)
|
+ Offset::new(-width / 2, height / 2)
|
||||||
+ Offset::y(Self::BASELINE_OFFSET);
|
+ Offset::y(Self::BASELINE_OFFSET);
|
||||||
display::text(
|
display::text_left(
|
||||||
start_of_baseline,
|
start_of_baseline,
|
||||||
text,
|
text,
|
||||||
style.font,
|
style.font,
|
||||||
|
@ -109,7 +109,7 @@ impl Component for Bip39Input {
|
|||||||
// Content starts in the left-center point, offset by 16px to the right and 8px
|
// Content starts in the left-center point, offset by 16px to the right and 8px
|
||||||
// to the bottom.
|
// to the bottom.
|
||||||
let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8);
|
let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8);
|
||||||
display::text(
|
display::text_left(
|
||||||
text_baseline,
|
text_baseline,
|
||||||
text,
|
text,
|
||||||
style.font,
|
style.font,
|
||||||
@ -120,7 +120,7 @@ impl Component for Bip39Input {
|
|||||||
// Paint the rest of the suggested dictionary word.
|
// Paint the rest of the suggested dictionary word.
|
||||||
if let Some(word) = self.suggested_word.and_then(|w| w.get(text.len()..)) {
|
if let Some(word) = self.suggested_word.and_then(|w| w.get(text.len()..)) {
|
||||||
let word_baseline = text_baseline + Offset::new(width, 0);
|
let word_baseline = text_baseline + Offset::new(width, 0);
|
||||||
display::text(
|
display::text_left(
|
||||||
word_baseline,
|
word_baseline,
|
||||||
word,
|
word,
|
||||||
style.font,
|
style.font,
|
||||||
|
@ -327,7 +327,7 @@ impl Component for Input {
|
|||||||
let ellipsis_width = style.text_font.text_width(ellipsis);
|
let ellipsis_width = style.text_font.text_width(ellipsis);
|
||||||
|
|
||||||
// Drawing the ellipsis and moving the baseline for the rest of the text.
|
// Drawing the ellipsis and moving the baseline for the rest of the text.
|
||||||
display::text(
|
display::text_left(
|
||||||
text_baseline,
|
text_baseline,
|
||||||
ellipsis,
|
ellipsis,
|
||||||
style.text_font,
|
style.text_font,
|
||||||
@ -346,7 +346,7 @@ impl Component for Input {
|
|||||||
&text[text.len() - chars_from_right..]
|
&text[text.len() - chars_from_right..]
|
||||||
};
|
};
|
||||||
|
|
||||||
display::text(
|
display::text_left(
|
||||||
text_baseline,
|
text_baseline,
|
||||||
text_to_display,
|
text_to_display,
|
||||||
style.text_font,
|
style.text_font,
|
||||||
|
@ -158,7 +158,7 @@ impl Component for Slip39Input {
|
|||||||
.assert_if_debugging_ui("Text buffer is too small");
|
.assert_if_debugging_ui("Text buffer is too small");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
display::text(
|
display::text_left(
|
||||||
text_baseline,
|
text_baseline,
|
||||||
text.as_str(),
|
text.as_str(),
|
||||||
style.font,
|
style.font,
|
||||||
|
@ -425,7 +425,7 @@ async def get_bool(
|
|||||||
data: str,
|
data: str,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
verb: str | None = "CONFIRM",
|
verb: str | None = "CONFIRM",
|
||||||
verb_cancel: str | None = "CANCEL",
|
verb_cancel: str | None = "",
|
||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@ -585,16 +585,27 @@ async def confirm_path_warning(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout:
|
||||||
|
content = RustLayout(
|
||||||
|
trezorui2.confirm_text(
|
||||||
|
title=title.upper(),
|
||||||
|
data=xpub,
|
||||||
|
# verb_cancel=cancel,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
async def show_xpub(
|
async def show_xpub(
|
||||||
ctx: wire.GenericContext, xpub: str, title: str, cancel: str
|
ctx: wire.GenericContext, xpub: str, title: str, cancel: str
|
||||||
) -> None:
|
) -> None:
|
||||||
return await _placeholder_confirm(
|
await raise_if_cancelled(
|
||||||
ctx=ctx,
|
interact(
|
||||||
br_type="show_xpub",
|
ctx,
|
||||||
title=title.upper(),
|
_show_xpub(xpub, title, cancel),
|
||||||
data=xpub,
|
"show_xpub",
|
||||||
description="",
|
ButtonRequestType.PublicKey,
|
||||||
br_code=ButtonRequestType.PublicKey,
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -611,20 +622,62 @@ async def show_address(
|
|||||||
address_extra: str | None = None,
|
address_extra: str | None = None,
|
||||||
title_qr: str | None = None,
|
title_qr: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
text = ""
|
is_multisig = len(xpubs) > 0
|
||||||
|
# TODO: replace with confirm_blob
|
||||||
|
data = address
|
||||||
if network:
|
if network:
|
||||||
text += f"{network} network\n"
|
data += f"\n\n{network}"
|
||||||
if address_extra:
|
if address_extra:
|
||||||
text += f"{address_extra}\n"
|
data += f"\n\n{address_extra}"
|
||||||
text += address
|
while True:
|
||||||
return await _placeholder_confirm(
|
result = await interact(
|
||||||
ctx=ctx,
|
ctx,
|
||||||
br_type="show_address",
|
RustLayout(
|
||||||
|
trezorui2.confirm_action(
|
||||||
title=title.upper(),
|
title=title.upper(),
|
||||||
data=text,
|
action=data,
|
||||||
description="",
|
description=None,
|
||||||
br_code=ButtonRequestType.Address,
|
verb="CONFIRM",
|
||||||
|
verb_cancel="QR",
|
||||||
|
reverse=False,
|
||||||
|
hold=False,
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
"show_address",
|
||||||
|
ButtonRequestType.Address,
|
||||||
|
)
|
||||||
|
if result is trezorui2.CONFIRMED:
|
||||||
|
break
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
RustLayout(
|
||||||
|
trezorui2.show_qr(
|
||||||
|
address=address if address_qr is None else address_qr,
|
||||||
|
case_sensitive=case_sensitive,
|
||||||
|
title=title.upper() if title_qr is None else title_qr.upper(),
|
||||||
|
verb_cancel="XPUBs" if is_multisig else "ADDRESS",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"show_qr",
|
||||||
|
ButtonRequestType.Address,
|
||||||
|
)
|
||||||
|
if result is trezorui2.CONFIRMED:
|
||||||
|
break
|
||||||
|
|
||||||
|
if is_multisig:
|
||||||
|
for i, xpub in enumerate(xpubs):
|
||||||
|
cancel = "NEXT" if i < len(xpubs) - 1 else "ADDRESS"
|
||||||
|
title_xpub = f"XPUB #{i + 1}"
|
||||||
|
title_xpub += " (yours)" if i == multisig_index else " (cosigner)"
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_show_xpub(xpub, title=title_xpub, cancel=cancel),
|
||||||
|
"show_xpub",
|
||||||
|
ButtonRequestType.PublicKey,
|
||||||
|
)
|
||||||
|
if result is trezorui2.CONFIRMED:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def show_pubkey(
|
def show_pubkey(
|
||||||
|
Loading…
Reference in New Issue
Block a user