mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-16 08:06:05 +00:00
fix(core): rebase on current drawlib
Port new render function signature to model_mercury. Fix some rebase errors. Also port recent changes from mp <-> rust iface. [no changelog]
This commit is contained in:
parent
fa4de43368
commit
55067a6d40
@ -182,7 +182,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
match self.current_page {
|
||||
0 => self.qr_code.render(target),
|
||||
1 => self.details.render(target),
|
||||
|
@ -8,8 +8,7 @@ use crate::{
|
||||
ui::{
|
||||
canvas::algo::PI4,
|
||||
component::{
|
||||
base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label,
|
||||
Split,
|
||||
base::Never, Bar, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split,
|
||||
},
|
||||
constant,
|
||||
display::loader::{loader_circular_uncompress, LoaderDimensions},
|
||||
@ -55,7 +54,7 @@ where
|
||||
style,
|
||||
)
|
||||
.vertically_centered();
|
||||
let bg = painter::rect_painter(style.background_color, theme::BG);
|
||||
let bg = Bar::new(style.background_color, theme::BG, 2);
|
||||
let inner = (bg, label);
|
||||
CoinJoinProgress::with_background(text, inner, indeterminate)
|
||||
}
|
||||
@ -133,7 +132,7 @@ where
|
||||
self.label.paint();
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.content.render(target);
|
||||
|
||||
let center = constant::screen().center() + Offset::y(LOADER_OFFSET);
|
||||
@ -141,10 +140,10 @@ where
|
||||
let background_color = theme::BG;
|
||||
let inactive_color = background_color.blend(active_color, 85);
|
||||
|
||||
let start = (self.value - 100) % 1000;
|
||||
let end = (self.value + 100) % 1000;
|
||||
let start = ((start as i32 * 8 * PI4 as i32) / 1000) as i16;
|
||||
let end = ((end as i32 * 8 * PI4 as i32) / 1000) as i16;
|
||||
let start = (self.value as i32 - 100) % 1000;
|
||||
let end = (self.value as i32 + 100) % 1000;
|
||||
let start = ((start * 8 * PI4 as i32) / 1000) as i16;
|
||||
let end = ((end * 8 * PI4 as i32) / 1000) as i16;
|
||||
|
||||
shape::Circle::new(center, LOADER_OUTER)
|
||||
.with_bg(inactive_color)
|
||||
|
@ -72,7 +72,7 @@ where
|
||||
self.controls.paint();
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.content.render(target);
|
||||
self.controls.render(target);
|
||||
}
|
||||
@ -204,7 +204,7 @@ where
|
||||
self.controls.paint();
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.image.render(target);
|
||||
self.paragraphs.render(target);
|
||||
self.controls.render(target);
|
||||
|
@ -12,6 +12,7 @@ use crate::ui::{
|
||||
};
|
||||
|
||||
use super::CancelConfirmMsg;
|
||||
use core::cell::Cell;
|
||||
|
||||
const ICON_HEIGHT: i16 = 70;
|
||||
const SCROLLBAR_INSET_TOP: i16 = 5;
|
||||
@ -32,7 +33,7 @@ pub struct FidoConfirm<F: Fn(usize) -> T, T, U> {
|
||||
/// Function/closure that will return appropriate page on demand.
|
||||
get_account: F,
|
||||
scrollbar: ScrollBar,
|
||||
fade: bool,
|
||||
fade: Cell<bool>,
|
||||
controls: U,
|
||||
}
|
||||
|
||||
@ -61,14 +62,28 @@ where
|
||||
page_swipe.allow_right = scrollbar.has_previous_page();
|
||||
page_swipe.allow_left = scrollbar.has_next_page();
|
||||
|
||||
// NOTE: This is an ugly hotfix for the erroneous behavior of
|
||||
// TextLayout used in the account_name Label. In this
|
||||
// particular case, TextLayout calculates the wrong height of
|
||||
// fitted text that's higher than the TextLayout bound itself.
|
||||
//
|
||||
// The following two lines should be swapped when the problem with
|
||||
// TextLayout is fixed.
|
||||
//
|
||||
// See also, continuation of this hotfix in the place() function.
|
||||
|
||||
// let current_account = get_account(scrollbar.active_page);
|
||||
let current_account = "".into();
|
||||
|
||||
|
||||
Self {
|
||||
app_name: Label::centered(app_name, theme::TEXT_DEMIBOLD),
|
||||
account_name: Label::centered("".into(), theme::TEXT_DEMIBOLD),
|
||||
account_name: Label::centered(current_account, theme::TEXT_DEMIBOLD),
|
||||
page_swipe,
|
||||
icon: Child::new(Image::new(icon_data)),
|
||||
get_account,
|
||||
scrollbar,
|
||||
fade: false,
|
||||
fade: Cell::new(false),
|
||||
controls,
|
||||
}
|
||||
}
|
||||
@ -89,11 +104,14 @@ where
|
||||
self.page_swipe.allow_right = self.scrollbar.has_previous_page();
|
||||
self.page_swipe.allow_left = self.scrollbar.has_next_page();
|
||||
|
||||
let current_account = (self.get_account)(self.active_page());
|
||||
self.account_name.set_text(current_account);
|
||||
|
||||
// Redraw the page.
|
||||
ctx.request_paint();
|
||||
|
||||
// Reset backlight to normal level on next paint.
|
||||
self.fade = true;
|
||||
self.fade.set(true);
|
||||
}
|
||||
|
||||
fn active_page(&self) -> usize {
|
||||
@ -141,6 +159,11 @@ where
|
||||
self.app_name.place(app_name_area);
|
||||
self.account_name.place(account_name_area);
|
||||
|
||||
// NOTE: This is a hotfix used due to the erroneous behavior of TextLayout.
|
||||
// This line should be removed when the problem with TextLayout is fixed.
|
||||
// See also the code for FidoConfirm::new().
|
||||
self.account_name.set_text((self.get_account)(self.scrollbar.active_page));
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
@ -168,8 +191,6 @@ where
|
||||
self.scrollbar.paint();
|
||||
}
|
||||
|
||||
let current_account = (self.get_account)(self.active_page());
|
||||
|
||||
// Erasing the old text content before writing the new one.
|
||||
let account_name_area = self.account_name.area();
|
||||
let real_area = account_name_area
|
||||
@ -179,21 +200,19 @@ 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.)
|
||||
if !current_account.as_ref().is_empty()
|
||||
&& current_account.as_ref() != self.app_name.text().as_ref()
|
||||
{
|
||||
self.account_name.set_text(current_account);
|
||||
let account_name = self.account_name.text().as_ref();
|
||||
let app_name = self.app_name.text().as_ref();
|
||||
if !account_name.is_empty() && account_name != app_name {
|
||||
self.account_name.paint();
|
||||
}
|
||||
|
||||
if self.fade {
|
||||
self.fade = false;
|
||||
if self.fade.take() {
|
||||
// Note that this is blocking and takes some time.
|
||||
display::fade_backlight(theme::BACKLIGHT_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.icon.render(target);
|
||||
self.controls.render(target);
|
||||
self.app_name.render(target);
|
||||
@ -202,8 +221,6 @@ where
|
||||
self.scrollbar.render(target);
|
||||
}
|
||||
|
||||
let current_account = (self.get_account)(self.active_page());
|
||||
|
||||
// Erasing the old text content before writing the new one.
|
||||
let account_name_area = self.account_name.area();
|
||||
let real_area = account_name_area
|
||||
@ -213,15 +230,13 @@ 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.)
|
||||
if !current_account.as_ref().is_empty()
|
||||
&& current_account.as_ref() != self.app_name.text().as_ref()
|
||||
{
|
||||
self.account_name.set_text(current_account);
|
||||
let account_name = self.account_name.text().as_ref();
|
||||
let app_name = self.app_name.text().as_ref();
|
||||
if !account_name.is_empty() && account_name != app_name {
|
||||
self.account_name.render(target);
|
||||
}
|
||||
|
||||
if self.fade {
|
||||
self.fade = false;
|
||||
if self.fade.take() {
|
||||
// Note that this is blocking and takes some time.
|
||||
display::fade_backlight(theme::BACKLIGHT_NORMAL);
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ impl Homescreen {
|
||||
self.loader.paint()
|
||||
}
|
||||
|
||||
fn render_loader(&mut self, target: &mut impl Renderer) {
|
||||
fn render_loader<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
TR::progress__locking_device.map_translated(|t| {
|
||||
shape::Text::new(TOP_CENTER + Offset::y(HOLD_Y), t)
|
||||
.with_align(Alignment::Center)
|
||||
@ -267,13 +267,13 @@ impl Component for Homescreen {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.pad.render(target);
|
||||
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
|
||||
self.render_loader(target);
|
||||
} else {
|
||||
let img_data = match self.custom_image {
|
||||
Some(ref img) => IMAGE_HOMESCREEN, //img.as_ref(), !@# solve lifetime problem
|
||||
Some(ref img) => img.as_ref(),
|
||||
None => IMAGE_HOMESCREEN,
|
||||
};
|
||||
|
||||
@ -458,9 +458,9 @@ impl Component for Lockscreen {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let img_data = match self.custom_image {
|
||||
Some(ref img) => IMAGE_HOMESCREEN, //img.as_ref(), !@# solve lifetime problem
|
||||
Some(ref img) => img.as_ref(),
|
||||
None => IMAGE_HOMESCREEN,
|
||||
};
|
||||
|
||||
|
@ -504,12 +504,12 @@ impl BlurringContext {
|
||||
fn vertical_avg(&mut self) {
|
||||
let lines = &mut self.mem.buffer[0..DECOMP_LINES];
|
||||
for i in 0..HOMESCREEN_IMAGE_WIDTH as usize {
|
||||
self.totals.buffer[RED_IDX][i] +=
|
||||
lines[self.add_idx][RED_IDX][i] - lines[self.rem_idx][RED_IDX][i];
|
||||
self.totals.buffer[GREEN_IDX][i] +=
|
||||
lines[self.add_idx][GREEN_IDX][i] - lines[self.rem_idx][GREEN_IDX][i];
|
||||
self.totals.buffer[BLUE_IDX][i] +=
|
||||
lines[self.add_idx][BLUE_IDX][i] - lines[self.rem_idx][BLUE_IDX][i];
|
||||
self.totals.buffer[RED_IDX][i] += lines[self.add_idx][RED_IDX][i];
|
||||
self.totals.buffer[GREEN_IDX][i] += lines[self.add_idx][GREEN_IDX][i];
|
||||
self.totals.buffer[BLUE_IDX][i] += lines[self.add_idx][BLUE_IDX][i];
|
||||
self.totals.buffer[RED_IDX][i] -= lines[self.rem_idx][RED_IDX][i];
|
||||
self.totals.buffer[GREEN_IDX][i] -= lines[self.rem_idx][GREEN_IDX][i];
|
||||
self.totals.buffer[BLUE_IDX][i] -= lines[self.rem_idx][BLUE_IDX][i];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ impl Component for Bip39Input {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let area = self.button.area();
|
||||
let style = self.button.style();
|
||||
|
||||
|
@ -131,8 +131,8 @@ pub fn paint_pending_marker(text_baseline: Point, text: &str, font: Font, color:
|
||||
}
|
||||
|
||||
/// Create a visible "underscoring" of the last letter of a text.
|
||||
pub fn render_pending_marker(
|
||||
target: &mut impl Renderer,
|
||||
pub fn render_pending_marker<'s>(
|
||||
target: &mut impl Renderer<'s>,
|
||||
text_baseline: Point,
|
||||
text: &str,
|
||||
font: Font,
|
||||
|
@ -183,12 +183,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.prompt.render(target);
|
||||
self.input.render(target);
|
||||
self.back.render(target);
|
||||
|
||||
for btn in &mut self.keys {
|
||||
for btn in &self.keys {
|
||||
btn.render(target);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ use crate::ui::{
|
||||
util::long_line_content_with_ellipsis,
|
||||
};
|
||||
|
||||
use core::cell::Cell;
|
||||
|
||||
pub enum PassphraseKeyboardMsg {
|
||||
Confirmed,
|
||||
Cancelled,
|
||||
@ -27,7 +29,7 @@ pub struct PassphraseKeyboard {
|
||||
confirm: Child<Button<&'static str>>,
|
||||
keys: [Child<Button<&'static str>>; KEY_COUNT],
|
||||
scrollbar: ScrollBar,
|
||||
fade: bool,
|
||||
fade: Cell<bool>,
|
||||
}
|
||||
|
||||
const STARTING_PAGE: usize = 1;
|
||||
@ -65,7 +67,7 @@ impl PassphraseKeyboard {
|
||||
Child::new(Button::new(Self::key_content(text)).styled(theme::button_pin()))
|
||||
}),
|
||||
scrollbar: ScrollBar::horizontal(),
|
||||
fade: false,
|
||||
fade: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +103,7 @@ impl PassphraseKeyboard {
|
||||
// Update buttons.
|
||||
self.replace_button_content(ctx, key_page);
|
||||
// Reset backlight to normal level on next paint.
|
||||
self.fade = true;
|
||||
self.fade.set(true);
|
||||
// So that swipe does not visually enable the input buttons when max length
|
||||
// reached
|
||||
self.update_input_btns_state(ctx);
|
||||
@ -290,23 +292,21 @@ impl Component for PassphraseKeyboard {
|
||||
for btn in &mut self.keys {
|
||||
btn.paint();
|
||||
}
|
||||
if self.fade {
|
||||
self.fade = false;
|
||||
if self.fade.take() {
|
||||
// Note that this is blocking and takes some time.
|
||||
display::fade_backlight(theme::BACKLIGHT_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.input.render(target);
|
||||
self.scrollbar.render(target);
|
||||
self.confirm.render(target);
|
||||
self.back.render(target);
|
||||
for btn in &mut self.keys {
|
||||
for btn in &self.keys {
|
||||
btn.render(target);
|
||||
}
|
||||
if self.fade {
|
||||
self.fade = false;
|
||||
if self.fade.take() {
|
||||
// Note that this is blocking and takes some time.
|
||||
display::fade_backlight(theme::BACKLIGHT_NORMAL);
|
||||
}
|
||||
@ -392,7 +392,7 @@ impl Component for Input {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let style = theme::label_keyboard();
|
||||
|
||||
let text_baseline = self.area.top_left() + Offset::y(style.text_font.text_height())
|
||||
|
@ -269,11 +269,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.erase_btn.render(target);
|
||||
self.textbox_pad.render(target);
|
||||
if self.textbox.inner().is_empty() {
|
||||
if let Some(ref mut w) = self.major_warning {
|
||||
if let Some(ref w) = self.major_warning {
|
||||
w.render(target);
|
||||
} else {
|
||||
self.major_prompt.render(target);
|
||||
@ -284,7 +284,7 @@ where
|
||||
self.textbox.render(target);
|
||||
}
|
||||
self.confirm_btn.render(target);
|
||||
for btn in &mut self.digit_btns {
|
||||
for btn in &self.digit_btns {
|
||||
btn.render(target);
|
||||
}
|
||||
}
|
||||
@ -389,7 +389,7 @@ impl PinDots {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_digits(&self, area: Rect, target: &mut impl Renderer) {
|
||||
fn render_digits<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) {
|
||||
let center = area.center() + Offset::y(Font::MONO.text_height() / 2);
|
||||
let right = center + Offset::x(Font::MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2);
|
||||
let digits = self.digits.len();
|
||||
@ -454,7 +454,7 @@ impl PinDots {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_dots(&self, area: Rect, target: &mut impl Renderer) {
|
||||
fn render_dots<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) {
|
||||
let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER);
|
||||
|
||||
let digits = self.digits.len();
|
||||
@ -533,7 +533,7 @@ impl Component for PinDots {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let dot_area = self.area.inset(HEADER_PADDING);
|
||||
self.pad.render(target);
|
||||
if self.display_digits {
|
||||
|
@ -187,7 +187,7 @@ impl Component for Slip39Input {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let area = self.button.area();
|
||||
let style = self.button.style();
|
||||
|
||||
|
@ -58,8 +58,8 @@ impl Component for SelectWordCount {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
for btn in self.button.iter_mut() {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
for btn in self.button.iter() {
|
||||
btn.render(target)
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ where
|
||||
self.confirm_button.paint();
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.input.render(target);
|
||||
self.paragraphs_pad.render(target);
|
||||
self.paragraphs.render(target);
|
||||
@ -240,7 +240,7 @@ impl Component for NumberInput {
|
||||
self.inc.paint();
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let mut buf = [0u8; 10];
|
||||
|
||||
if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) {
|
||||
|
@ -18,6 +18,8 @@ use super::{
|
||||
SwipeDirection,
|
||||
};
|
||||
|
||||
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> {
|
||||
@ -41,7 +43,7 @@ pub struct ButtonPage<T, U> {
|
||||
/// Whether to pass-through right swipe to parent component.
|
||||
swipe_right: bool,
|
||||
/// Fade to given backlight level on next paint().
|
||||
fade: Option<u16>,
|
||||
fade: Cell<Option<u16>>,
|
||||
}
|
||||
|
||||
impl<T> ButtonPage<T, StrBuffer>
|
||||
@ -77,7 +79,7 @@ where
|
||||
cancel_from_any_page: false,
|
||||
swipe_left: false,
|
||||
swipe_right: false,
|
||||
fade: None,
|
||||
fade: Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,7 +159,7 @@ where
|
||||
|
||||
// Swipe has dimmed the screen, so fade back to normal backlight after the next
|
||||
// paint.
|
||||
self.fade = Some(theme::BACKLIGHT_NORMAL);
|
||||
self.fade.set(Some(theme::BACKLIGHT_NORMAL));
|
||||
}
|
||||
|
||||
fn is_cancel_visible(&self) -> bool {
|
||||
@ -413,7 +415,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.pad.render(target);
|
||||
match &self.loader {
|
||||
Some(l) if l.is_animating() => self.loader.render(target),
|
||||
|
@ -123,7 +123,7 @@ where
|
||||
self.description.paint();
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.title.render(target);
|
||||
|
||||
let center = constant::screen().center() + Offset::y(self.loader_y_offset);
|
||||
|
@ -124,7 +124,7 @@ impl Component for ScrollBar {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
fn dotsize(distance: usize, nhidden: usize) -> Icon {
|
||||
match (nhidden.saturating_sub(distance)).min(2 - distance) {
|
||||
0 => theme::DOT_INACTIVE,
|
||||
|
@ -6,6 +6,7 @@ use crate::ui::{
|
||||
};
|
||||
|
||||
use super::{theme, ScrollBar, Swipe, SwipeDirection};
|
||||
use core::cell::Cell;
|
||||
|
||||
const SCROLLBAR_HEIGHT: i16 = 18;
|
||||
const SCROLLBAR_BORDER: i16 = 4;
|
||||
@ -17,7 +18,7 @@ pub struct SimplePage<T> {
|
||||
scrollbar: ScrollBar,
|
||||
axis: Axis,
|
||||
swipe_right_to_go_back: bool,
|
||||
fade: Option<u16>,
|
||||
fade: Cell<Option<u16>>,
|
||||
}
|
||||
|
||||
impl<T> SimplePage<T>
|
||||
@ -33,7 +34,7 @@ where
|
||||
scrollbar: ScrollBar::new(axis),
|
||||
axis,
|
||||
swipe_right_to_go_back: false,
|
||||
fade: None,
|
||||
fade: Cell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +81,7 @@ where
|
||||
|
||||
// Swipe has dimmed the screen, so fade back to normal backlight after the next
|
||||
// paint.
|
||||
self.fade = Some(theme::BACKLIGHT_NORMAL);
|
||||
self.fade.set(Some(theme::BACKLIGHT_NORMAL));
|
||||
}
|
||||
|
||||
fn is_horizontal(&self) -> bool {
|
||||
@ -165,7 +166,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&mut self, target: &mut impl Renderer) {
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
self.pad.render(target);
|
||||
self.content.render(target);
|
||||
if self.scrollbar.has_pages() {
|
||||
|
@ -161,5 +161,5 @@ impl Component for Swipe {
|
||||
|
||||
fn paint(&mut self) {}
|
||||
|
||||
fn render(&mut self, _target: &mut impl Renderer) {}
|
||||
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ use crate::{
|
||||
base::ComponentExt,
|
||||
connect::Connect,
|
||||
image::BlendedImage,
|
||||
jpeg::{ImageBuffer, Jpeg},
|
||||
paginated::{PageMsg, Paginate},
|
||||
painter,
|
||||
placed::GridPlaced,
|
||||
text::{
|
||||
op::OpTextLayout,
|
||||
@ -211,10 +211,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> ComponentMsgObj for painter::Painter<F>
|
||||
where
|
||||
F: FnMut(geometry::Rect),
|
||||
{
|
||||
impl ComponentMsgObj for Jpeg {
|
||||
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||
unreachable!()
|
||||
}
|
||||
@ -646,33 +643,25 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m
|
||||
extern "C" fn new_confirm_homescreen(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 data: Obj = kwargs.get(Qstr::MP_QSTR_image)?;
|
||||
let image: Obj = kwargs.get(Qstr::MP_QSTR_image)?;
|
||||
|
||||
// Layout needs to hold the Obj to play nice with GC. Obj is resolved to &[u8]
|
||||
// in every paint pass.
|
||||
let buffer_func = move || {
|
||||
// SAFETY: We expect no existing mutable reference. Resulting reference is
|
||||
// discarded before returning to micropython.
|
||||
let buffer = unsafe { unwrap!(get_buffer(data)) };
|
||||
// Incoming data may be empty, meaning we should display default homescreen
|
||||
// image.
|
||||
if buffer.is_empty() {
|
||||
theme::IMAGE_HOMESCREEN
|
||||
} else {
|
||||
buffer
|
||||
}
|
||||
};
|
||||
let mut jpeg = unwrap!(ImageBuffer::from_object(image));
|
||||
|
||||
let size = match jpeg_info(buffer_func()) {
|
||||
Some(info) => info.0,
|
||||
_ => return Err(value_error!("Invalid image.")),
|
||||
if jpeg.is_empty() {
|
||||
// Incoming data may be empty, meaning we should
|
||||
// display default homescreen image.
|
||||
jpeg = ImageBuffer::from_slice(theme::IMAGE_HOMESCREEN);
|
||||
}
|
||||
|
||||
if let None = jpeg_info(jpeg.data()) {
|
||||
return Err(value_error!("Invalid image."));
|
||||
};
|
||||
|
||||
let tr_change: StrBuffer = TR::buttons__change.try_into()?;
|
||||
let buttons = Button::cancel_confirm_text(None, Some(tr_change));
|
||||
let obj = LayoutObj::new(Frame::centered(
|
||||
title,
|
||||
Dialog::new(painter::jpeg_painter(buffer_func, size, 1), buttons),
|
||||
Dialog::new(Jpeg::new(jpeg, 1), buttons),
|
||||
))?;
|
||||
Ok(obj.into())
|
||||
};
|
||||
@ -1662,15 +1651,86 @@ extern "C" fn new_show_wait_text(message: Obj) -> Obj {
|
||||
|
||||
#[no_mangle]
|
||||
pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// from trezor import utils
|
||||
///
|
||||
/// T = TypeVar("T")
|
||||
///
|
||||
/// class LayoutObj(Generic[T]):
|
||||
/// """Representation of a Rust-based layout object.
|
||||
/// see `trezor::ui::layout::obj::LayoutObj`.
|
||||
/// """
|
||||
///
|
||||
/// def attach_timer_fn(self, fn: Callable[[int, int], None]) -> None:
|
||||
/// """Attach a timer setter function.
|
||||
///
|
||||
/// The layout object can call the timer setter with two arguments,
|
||||
/// `token` and `deadline`. When `deadline` is reached, the layout object
|
||||
/// expects a callback to `self.timer(token)`.
|
||||
/// """
|
||||
///
|
||||
/// if utils.USE_TOUCH:
|
||||
/// def touch_event(self, event: int, x: int, y: int) -> T | None:
|
||||
/// """Receive a touch event `event` at coordinates `x`, `y`."""
|
||||
///
|
||||
/// if utils.USE_BUTTON:
|
||||
/// def button_event(self, event: int, button: int) -> T | None:
|
||||
/// """Receive a button event `event` for button `button`."""
|
||||
///
|
||||
/// def progress_event(self, value: int, description: str) -> T | None:
|
||||
/// """Receive a progress event."""
|
||||
///
|
||||
/// def usb_event(self, connected: bool) -> T | None:
|
||||
/// """Receive a USB connect/disconnect event."""
|
||||
///
|
||||
/// def timer(self, token: int) -> T | None:
|
||||
/// """Callback for the timer set by `attach_timer_fn`.
|
||||
///
|
||||
/// This function should be called by the executor after the corresponding
|
||||
/// deadline is reached.
|
||||
/// """
|
||||
///
|
||||
/// def paint(self) -> bool:
|
||||
/// """Paint the layout object on screen.
|
||||
///
|
||||
/// Will only paint updated parts of the layout as required.
|
||||
/// Returns True if any painting actually happened.
|
||||
/// """
|
||||
///
|
||||
/// def request_complete_repaint(self) -> None:
|
||||
/// """Request a complete repaint of the screen.
|
||||
///
|
||||
/// Does not repaint the screen, a subsequent call to `paint()` is required.
|
||||
/// """
|
||||
///
|
||||
/// if __debug__:
|
||||
/// def trace(self, tracer: Callable[[str], None]) -> None:
|
||||
/// """Generate a JSON trace of the layout object.
|
||||
///
|
||||
/// The JSON can be emitted as a sequence of calls to `tracer`, each of
|
||||
/// which is not necessarily a valid JSON chunk. The caller must
|
||||
/// reassemble the chunks to get a sensible result.
|
||||
/// """
|
||||
///
|
||||
/// def bounds(self) -> None:
|
||||
/// """Paint bounds of individual components on screen."""
|
||||
///
|
||||
/// def page_count(self) -> int:
|
||||
/// """Return the number of pages in the layout object."""
|
||||
///
|
||||
/// class UiResult:
|
||||
/// """Result of a UI operation."""
|
||||
/// pass
|
||||
///
|
||||
/// mock:global
|
||||
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(),
|
||||
|
||||
/// CONFIRMED: object
|
||||
/// CONFIRMED: UiResult
|
||||
Qstr::MP_QSTR_CONFIRMED => CONFIRMED.as_obj(),
|
||||
|
||||
/// CANCELLED: object
|
||||
/// CANCELLED: UiResult
|
||||
Qstr::MP_QSTR_CANCELLED => CANCELLED.as_obj(),
|
||||
|
||||
/// INFO: object
|
||||
/// INFO: UiResult
|
||||
Qstr::MP_QSTR_INFO => INFO.as_obj(),
|
||||
|
||||
/// def disable_animation(disable: bool) -> None:
|
||||
@ -1691,7 +1751,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// hold: bool = False,
|
||||
/// hold_danger: bool = False,
|
||||
/// reverse: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm action."""
|
||||
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
|
||||
|
||||
@ -1700,7 +1760,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// title: str,
|
||||
/// items: Iterable[str | tuple[bool, str]],
|
||||
/// verb: str | None = None,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm formatted text that has been pre-split in python. For tuples
|
||||
/// the first component is a bool indicating whether this part is emphasized."""
|
||||
Qstr::MP_QSTR_confirm_emphasized => obj_fn_kw!(0, new_confirm_emphasized).as_obj(),
|
||||
@ -1709,7 +1769,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// image: bytes,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm homescreen."""
|
||||
Qstr::MP_QSTR_confirm_homescreen => obj_fn_kw!(0, new_confirm_homescreen).as_obj(),
|
||||
|
||||
@ -1723,7 +1783,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// verb_cancel: str | None = None,
|
||||
/// hold: bool = False,
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm byte sequence data."""
|
||||
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).as_obj(),
|
||||
|
||||
@ -1735,7 +1795,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// verb: str | None = "CONFIRM",
|
||||
/// extra: str | None,
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm address. Similar to `confirm_blob` but has corner info button
|
||||
/// and allows left swipe which does the same thing as the button."""
|
||||
Qstr::MP_QSTR_confirm_address => obj_fn_kw!(0, new_confirm_address).as_obj(),
|
||||
@ -1745,7 +1805,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// title: str,
|
||||
/// items: list[tuple[str | None, str | bytes | None, bool]],
|
||||
/// hold: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm list of key-value pairs. The third component in the tuple should be True if
|
||||
/// the value is to be rendered as binary with monospace font, False otherwise."""
|
||||
Qstr::MP_QSTR_confirm_properties => obj_fn_kw!(0, new_confirm_properties).as_obj(),
|
||||
@ -1754,7 +1814,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// button: str,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm TOS before device setup."""
|
||||
Qstr::MP_QSTR_confirm_reset_device => obj_fn_kw!(0, new_confirm_reset_device).as_obj(),
|
||||
|
||||
@ -1767,7 +1827,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// account: str | None,
|
||||
/// path: str | None,
|
||||
/// xpubs: list[tuple[str, str]],
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show address details - QR code, account, path, cosigner xpubs."""
|
||||
Qstr::MP_QSTR_show_address_details => obj_fn_kw!(0, new_show_address_details).as_obj(),
|
||||
|
||||
@ -1777,7 +1837,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// items: Iterable[Tuple[str, str]],
|
||||
/// horizontal: bool = False,
|
||||
/// chunkify: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show metadata for outgoing transaction."""
|
||||
Qstr::MP_QSTR_show_info_with_cancel => obj_fn_kw!(0, new_show_info_with_cancel).as_obj(),
|
||||
|
||||
@ -1793,7 +1853,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// hold: bool = False,
|
||||
/// chunkify: bool = False,
|
||||
/// text_mono: bool = True,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm value. Merge of confirm_total and confirm_output."""
|
||||
Qstr::MP_QSTR_confirm_value => obj_fn_kw!(0, new_confirm_value).as_obj(),
|
||||
|
||||
@ -1803,7 +1863,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// items: Iterable[tuple[str, str]],
|
||||
/// info_button: bool = False,
|
||||
/// cancel_arrow: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Transaction summary. Always hold to confirm."""
|
||||
Qstr::MP_QSTR_confirm_total => obj_fn_kw!(0, new_confirm_total).as_obj(),
|
||||
|
||||
@ -1812,7 +1872,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// sign: int,
|
||||
/// amount_change: str,
|
||||
/// amount_new: str,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Decrease or increase output amount."""
|
||||
Qstr::MP_QSTR_confirm_modify_output => obj_fn_kw!(0, new_confirm_modify_output).as_obj(),
|
||||
|
||||
@ -1823,7 +1883,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// user_fee_change: str,
|
||||
/// total_fee_new: str,
|
||||
/// fee_rate_amount: str | None, # ignored
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Decrease or increase transaction fee."""
|
||||
Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(),
|
||||
|
||||
@ -1833,7 +1893,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// app_name: str,
|
||||
/// icon_name: str | None,
|
||||
/// accounts: list[str | None],
|
||||
/// ) -> int | object:
|
||||
/// ) -> LayoutObj[int | UiResult]:
|
||||
/// """FIDO confirmation.
|
||||
///
|
||||
/// Returns page index in case of confirmation and CANCELLED otherwise.
|
||||
@ -1847,7 +1907,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Error modal. No buttons shown when `button` is empty string."""
|
||||
Qstr::MP_QSTR_show_error => obj_fn_kw!(0, new_show_error).as_obj(),
|
||||
|
||||
@ -1859,7 +1919,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Warning modal. No buttons shown when `button` is empty string."""
|
||||
Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(),
|
||||
|
||||
@ -1870,7 +1930,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Success modal. No buttons shown when `button` is empty string."""
|
||||
Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(),
|
||||
|
||||
@ -1881,11 +1941,11 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// description: str = "",
|
||||
/// allow_cancel: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Info modal. No buttons shown when `button` is empty string."""
|
||||
Qstr::MP_QSTR_show_info => obj_fn_kw!(0, new_show_info).as_obj(),
|
||||
|
||||
/// def show_mismatch(*, title: str) -> object:
|
||||
/// def show_mismatch(*, title: str) -> LayoutObj[UiResult]:
|
||||
/// """Warning modal, receiving address mismatch."""
|
||||
Qstr::MP_QSTR_show_mismatch => obj_fn_kw!(0, new_show_mismatch).as_obj(),
|
||||
|
||||
@ -1894,7 +1954,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// title: str | None,
|
||||
/// description: str = "",
|
||||
/// button: str = "",
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Simple dialog with text and one button."""
|
||||
Qstr::MP_QSTR_show_simple => obj_fn_kw!(0, new_show_simple).as_obj(),
|
||||
|
||||
@ -1904,7 +1964,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// button: str,
|
||||
/// info_button: str,
|
||||
/// items: Iterable[tuple[int, str]],
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm given items but with third button. Always single page
|
||||
/// without scrolling."""
|
||||
Qstr::MP_QSTR_confirm_with_info => obj_fn_kw!(0, new_confirm_with_info).as_obj(),
|
||||
@ -1914,7 +1974,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// title: str,
|
||||
/// button: str,
|
||||
/// items: Iterable[tuple[int, str]],
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm long content with the possibility to go back from any page.
|
||||
/// Meant to be used with confirm_with_info."""
|
||||
Qstr::MP_QSTR_confirm_more => obj_fn_kw!(0, new_confirm_more).as_obj(),
|
||||
@ -1923,7 +1983,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// *,
|
||||
/// max_rounds: str,
|
||||
/// max_feerate: str,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Confirm coinjoin authorization."""
|
||||
Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(),
|
||||
|
||||
@ -1933,7 +1993,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// subprompt: str,
|
||||
/// allow_cancel: bool = True,
|
||||
/// wrong_pin: bool = False,
|
||||
/// ) -> str | object:
|
||||
/// ) -> LayoutObj[str | UiResult]:
|
||||
/// """Request pin on device."""
|
||||
Qstr::MP_QSTR_request_pin => obj_fn_kw!(0, new_request_pin).as_obj(),
|
||||
|
||||
@ -1941,7 +2001,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// *,
|
||||
/// prompt: str,
|
||||
/// max_len: int,
|
||||
/// ) -> str | object:
|
||||
/// ) -> LayoutObj[str | UiResult]:
|
||||
/// """Passphrase input keyboard."""
|
||||
Qstr::MP_QSTR_request_passphrase => obj_fn_kw!(0, new_request_passphrase).as_obj(),
|
||||
|
||||
@ -1950,7 +2010,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// prompt: str,
|
||||
/// prefill_word: str,
|
||||
/// can_go_back: bool,
|
||||
/// ) -> str:
|
||||
/// ) -> LayoutObj[str]:
|
||||
/// """BIP39 word input keyboard."""
|
||||
Qstr::MP_QSTR_request_bip39 => obj_fn_kw!(0, new_request_bip39).as_obj(),
|
||||
|
||||
@ -1959,7 +2019,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// prompt: str,
|
||||
/// prefill_word: str,
|
||||
/// can_go_back: bool,
|
||||
/// ) -> str:
|
||||
/// ) -> LayoutObj[str]:
|
||||
/// """SLIP39 word input keyboard."""
|
||||
Qstr::MP_QSTR_request_slip39 => obj_fn_kw!(0, new_request_slip39).as_obj(),
|
||||
|
||||
@ -1968,7 +2028,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// title: str,
|
||||
/// description: str,
|
||||
/// words: Iterable[str],
|
||||
/// ) -> int:
|
||||
/// ) -> LayoutObj[int]:
|
||||
/// """Select mnemonic word from three possibilities - seed check after backup. The
|
||||
/// iterable must be of exact size. Returns index in range `0..3`."""
|
||||
Qstr::MP_QSTR_select_word => obj_fn_kw!(0, new_select_word).as_obj(),
|
||||
@ -1977,7 +2037,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// *,
|
||||
/// title: str,
|
||||
/// pages: Iterable[str],
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show mnemonic for backup. Expects the words pre-divided into individual pages."""
|
||||
Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(),
|
||||
|
||||
@ -1988,7 +2048,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// min_count: int,
|
||||
/// max_count: int,
|
||||
/// description: Callable[[int], str] | None = None,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[tuple[UiResult, int]]:
|
||||
/// """Number input with + and - buttons, description, and info button."""
|
||||
Qstr::MP_QSTR_request_number => obj_fn_kw!(0, new_request_number).as_obj(),
|
||||
|
||||
@ -1998,7 +2058,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// items: Iterable[str],
|
||||
/// active: int,
|
||||
/// button: str,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Checklist of backup steps. Active index is highlighted, previous items have check
|
||||
/// mark next to them."""
|
||||
Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(),
|
||||
@ -2010,28 +2070,28 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// button: str,
|
||||
/// dry_run: bool,
|
||||
/// info_button: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Device recovery homescreen."""
|
||||
Qstr::MP_QSTR_confirm_recovery => obj_fn_kw!(0, new_confirm_recovery).as_obj(),
|
||||
|
||||
/// def select_word_count(
|
||||
/// *,
|
||||
/// dry_run: bool,
|
||||
/// ) -> int | str: # TT returns int
|
||||
/// ) -> LayoutObj[int | str]: # TT returns int
|
||||
/// """Select mnemonic word count from (12, 18, 20, 24, 33)."""
|
||||
Qstr::MP_QSTR_select_word_count => obj_fn_kw!(0, new_select_word_count).as_obj(),
|
||||
|
||||
/// def show_group_share_success(
|
||||
/// *,
|
||||
/// lines: Iterable[str]
|
||||
/// ) -> int:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Shown after successfully finishing a group."""
|
||||
Qstr::MP_QSTR_show_group_share_success => obj_fn_kw!(0, new_show_group_share_success).as_obj(),
|
||||
|
||||
/// def show_remaining_shares(
|
||||
/// *,
|
||||
/// pages: Iterable[tuple[str, str]],
|
||||
/// ) -> int:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
||||
Qstr::MP_QSTR_show_remaining_shares => obj_fn_kw!(0, new_show_remaining_shares).as_obj(),
|
||||
|
||||
@ -2040,7 +2100,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// title: str,
|
||||
/// indeterminate: bool = False,
|
||||
/// description: str = "",
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show progress loader. Please note that the number of lines reserved on screen for
|
||||
/// description is determined at construction time. If you want multiline descriptions
|
||||
/// make sure the initial description has at least that amount of lines."""
|
||||
@ -2052,7 +2112,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// indeterminate: bool = False,
|
||||
/// time_ms: int = 0,
|
||||
/// skip_first_paint: bool = False,
|
||||
/// ) -> object:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Show progress loader for coinjoin. Returns CANCELLED after a specified time when
|
||||
/// time_ms timeout is passed."""
|
||||
Qstr::MP_QSTR_show_progress_coinjoin => obj_fn_kw!(0, new_show_progress_coinjoin).as_obj(),
|
||||
@ -2064,7 +2124,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// notification: str | None,
|
||||
/// notification_level: int = 0,
|
||||
/// skip_first_paint: bool,
|
||||
/// ) -> CANCELLED:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Idle homescreen."""
|
||||
Qstr::MP_QSTR_show_homescreen => obj_fn_kw!(0, new_show_homescreen).as_obj(),
|
||||
|
||||
@ -2074,7 +2134,7 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// bootscreen: bool,
|
||||
/// skip_first_paint: bool,
|
||||
/// coinjoin_authorized: bool = False,
|
||||
/// ) -> CANCELLED:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Homescreen for locked device."""
|
||||
Qstr::MP_QSTR_show_lockscreen => obj_fn_kw!(0, new_show_lockscreen).as_obj(),
|
||||
|
||||
@ -2082,11 +2142,11 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
||||
/// *,
|
||||
/// description: str,
|
||||
/// fingerprint: str,
|
||||
/// ) -> None:
|
||||
/// ) -> LayoutObj[UiResult]:
|
||||
/// """Ask whether to update firmware, optionally show fingerprint. Shared with bootloader."""
|
||||
Qstr::MP_QSTR_confirm_firmware_update => obj_fn_kw!(0, new_confirm_firmware_update).as_obj(),
|
||||
|
||||
/// def show_wait_text(/, message: str) -> None:
|
||||
/// def show_wait_text(message: str, /) -> LayoutObj[None]:
|
||||
/// """Show single-line text in the middle of the screen."""
|
||||
Qstr::MP_QSTR_show_wait_text => obj_fn_1!(new_show_wait_text).as_obj(),
|
||||
};
|
||||
|
@ -9,9 +9,9 @@ use crate::trezorhal::bitmap::{BitmapView, Dma2d};
|
||||
|
||||
use static_alloc::Bump;
|
||||
|
||||
pub fn render_on_display<F>(clip: Option<Rect>, bg_color: Option<Color>, mut func: F)
|
||||
pub fn render_on_display<'a, F>(clip: Option<Rect>, bg_color: Option<Color>, func: F)
|
||||
where
|
||||
F: FnMut(&mut ProgressiveRenderer<Bump<[u8; 40 * 1024]>, DisplayModelMercury>),
|
||||
F: FnOnce(&mut ProgressiveRenderer<'_, 'a, Bump<[u8; 40 * 1024]>, DisplayModelMercury>),
|
||||
{
|
||||
#[link_section = ".no_dma_buffers"]
|
||||
static mut BUMP_A: Bump<[u8; 40 * 1024]> = Bump::uninit();
|
||||
@ -22,6 +22,9 @@ where
|
||||
let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) };
|
||||
let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) };
|
||||
{
|
||||
bump_a.reset();
|
||||
bump_b.reset();
|
||||
|
||||
let cache = DrawingCache::new(bump_a, bump_b);
|
||||
let mut canvas = DisplayModelMercury::acquire().unwrap();
|
||||
|
||||
@ -29,14 +32,12 @@ where
|
||||
canvas.set_viewport(Viewport::new(clip));
|
||||
}
|
||||
|
||||
let mut target = ProgressiveRenderer::new(&mut canvas, bg_color, &cache, bump_a, 30);
|
||||
let mut target = ProgressiveRenderer::new(&mut canvas, bg_color, &cache, bump_a, 45);
|
||||
|
||||
func(&mut target);
|
||||
|
||||
target.render(16);
|
||||
}
|
||||
bump_a.reset();
|
||||
bump_b.reset();
|
||||
}
|
||||
|
||||
pub struct DisplayModelMercury {
|
||||
@ -65,7 +66,7 @@ impl BasicCanvas for DisplayModelMercury {
|
||||
self.size
|
||||
}
|
||||
|
||||
fn fill_rect(&mut self, r: Rect, color: Color) {
|
||||
fn fill_rect(&mut self, r: Rect, color: Color, _alpha: u8) {
|
||||
let r = r.translate(self.viewport.origin);
|
||||
Dma2d::wnd565_fill(r, self.viewport.clip, color);
|
||||
}
|
||||
|
@ -1,4 +1,60 @@
|
||||
from typing import *
|
||||
from trezor import utils
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
class LayoutObj(Generic[T]):
|
||||
"""Representation of a Rust-based layout object.
|
||||
see `trezor::ui::layout::obj::LayoutObj`.
|
||||
"""
|
||||
def attach_timer_fn(self, fn: Callable[[int, int], None]) -> None:
|
||||
"""Attach a timer setter function.
|
||||
The layout object can call the timer setter with two arguments,
|
||||
`token` and `deadline`. When `deadline` is reached, the layout object
|
||||
expects a callback to `self.timer(token)`.
|
||||
"""
|
||||
if utils.USE_TOUCH:
|
||||
def touch_event(self, event: int, x: int, y: int) -> T | None:
|
||||
"""Receive a touch event `event` at coordinates `x`, `y`."""
|
||||
if utils.USE_BUTTON:
|
||||
def button_event(self, event: int, button: int) -> T | None:
|
||||
"""Receive a button event `event` for button `button`."""
|
||||
def progress_event(self, value: int, description: str) -> T | None:
|
||||
"""Receive a progress event."""
|
||||
def usb_event(self, connected: bool) -> T | None:
|
||||
"""Receive a USB connect/disconnect event."""
|
||||
def timer(self, token: int) -> T | None:
|
||||
"""Callback for the timer set by `attach_timer_fn`.
|
||||
This function should be called by the executor after the corresponding
|
||||
deadline is reached.
|
||||
"""
|
||||
def paint(self) -> bool:
|
||||
"""Paint the layout object on screen.
|
||||
Will only paint updated parts of the layout as required.
|
||||
Returns True if any painting actually happened.
|
||||
"""
|
||||
def request_complete_repaint(self) -> None:
|
||||
"""Request a complete repaint of the screen.
|
||||
Does not repaint the screen, a subsequent call to `paint()` is required.
|
||||
"""
|
||||
if __debug__:
|
||||
def trace(self, tracer: Callable[[str], None]) -> None:
|
||||
"""Generate a JSON trace of the layout object.
|
||||
The JSON can be emitted as a sequence of calls to `tracer`, each of
|
||||
which is not necessarily a valid JSON chunk. The caller must
|
||||
reassemble the chunks to get a sensible result.
|
||||
"""
|
||||
def bounds(self) -> None:
|
||||
"""Paint bounds of individual components on screen."""
|
||||
def page_count(self) -> int:
|
||||
"""Return the number of pages in the layout object."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
class UiResult:
|
||||
"""Result of a UI operation."""
|
||||
pass
|
||||
CONFIRMED: UiResult
|
||||
CANCELLED: UiResult
|
||||
INFO: UiResult
|
||||
@ -25,7 +81,7 @@ def confirm_action(
|
||||
hold: bool = False,
|
||||
hold_danger: bool = False,
|
||||
reverse: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm action."""
|
||||
|
||||
|
||||
@ -35,7 +91,7 @@ def confirm_emphasized(
|
||||
title: str,
|
||||
items: Iterable[str | tuple[bool, str]],
|
||||
verb: str | None = None,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm formatted text that has been pre-split in python. For tuples
|
||||
the first component is a bool indicating whether this part is emphasized."""
|
||||
|
||||
@ -45,7 +101,7 @@ def confirm_homescreen(
|
||||
*,
|
||||
title: str,
|
||||
image: bytes,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm homescreen."""
|
||||
|
||||
|
||||
@ -60,7 +116,7 @@ def confirm_blob(
|
||||
verb_cancel: str | None = None,
|
||||
hold: bool = False,
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm byte sequence data."""
|
||||
|
||||
|
||||
@ -73,7 +129,7 @@ def confirm_address(
|
||||
verb: str | None = "CONFIRM",
|
||||
extra: str | None,
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm address. Similar to `confirm_blob` but has corner info button
|
||||
and allows left swipe which does the same thing as the button."""
|
||||
|
||||
@ -84,7 +140,7 @@ def confirm_properties(
|
||||
title: str,
|
||||
items: list[tuple[str | None, str | bytes | None, bool]],
|
||||
hold: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm list of key-value pairs. The third component in the tuple should be True if
|
||||
the value is to be rendered as binary with monospace font, False otherwise."""
|
||||
|
||||
@ -94,7 +150,7 @@ def confirm_reset_device(
|
||||
*,
|
||||
title: str,
|
||||
button: str,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm TOS before device setup."""
|
||||
|
||||
|
||||
@ -108,7 +164,7 @@ def show_address_details(
|
||||
account: str | None,
|
||||
path: str | None,
|
||||
xpubs: list[tuple[str, str]],
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show address details - QR code, account, path, cosigner xpubs."""
|
||||
|
||||
|
||||
@ -119,7 +175,7 @@ def show_info_with_cancel(
|
||||
items: Iterable[Tuple[str, str]],
|
||||
horizontal: bool = False,
|
||||
chunkify: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show metadata for outgoing transaction."""
|
||||
|
||||
|
||||
@ -136,7 +192,7 @@ def confirm_value(
|
||||
hold: bool = False,
|
||||
chunkify: bool = False,
|
||||
text_mono: bool = True,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm value. Merge of confirm_total and confirm_output."""
|
||||
|
||||
|
||||
@ -147,7 +203,7 @@ def confirm_total(
|
||||
items: Iterable[tuple[str, str]],
|
||||
info_button: bool = False,
|
||||
cancel_arrow: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Transaction summary. Always hold to confirm."""
|
||||
|
||||
|
||||
@ -157,7 +213,7 @@ def confirm_modify_output(
|
||||
sign: int,
|
||||
amount_change: str,
|
||||
amount_new: str,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Decrease or increase output amount."""
|
||||
|
||||
|
||||
@ -169,7 +225,7 @@ def confirm_modify_fee(
|
||||
user_fee_change: str,
|
||||
total_fee_new: str,
|
||||
fee_rate_amount: str | None, # ignored
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Decrease or increase transaction fee."""
|
||||
|
||||
|
||||
@ -180,7 +236,7 @@ def confirm_fido(
|
||||
app_name: str,
|
||||
icon_name: str | None,
|
||||
accounts: list[str | None],
|
||||
) -> int | object:
|
||||
) -> LayoutObj[int | UiResult]:
|
||||
"""FIDO confirmation.
|
||||
Returns page index in case of confirmation and CANCELLED otherwise.
|
||||
"""
|
||||
@ -194,7 +250,7 @@ def show_error(
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Error modal. No buttons shown when `button` is empty string."""
|
||||
|
||||
|
||||
@ -207,7 +263,7 @@ def show_warning(
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Warning modal. No buttons shown when `button` is empty string."""
|
||||
|
||||
|
||||
@ -219,7 +275,7 @@ def show_success(
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Success modal. No buttons shown when `button` is empty string."""
|
||||
|
||||
|
||||
@ -231,12 +287,12 @@ def show_info(
|
||||
description: str = "",
|
||||
allow_cancel: bool = False,
|
||||
time_ms: int = 0,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Info modal. No buttons shown when `button` is empty string."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
def show_mismatch(*, title: str) -> object:
|
||||
def show_mismatch(*, title: str) -> LayoutObj[UiResult]:
|
||||
"""Warning modal, receiving address mismatch."""
|
||||
|
||||
|
||||
@ -246,7 +302,7 @@ def show_simple(
|
||||
title: str | None,
|
||||
description: str = "",
|
||||
button: str = "",
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Simple dialog with text and one button."""
|
||||
|
||||
|
||||
@ -257,7 +313,7 @@ def confirm_with_info(
|
||||
button: str,
|
||||
info_button: str,
|
||||
items: Iterable[tuple[int, str]],
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm given items but with third button. Always single page
|
||||
without scrolling."""
|
||||
|
||||
@ -268,7 +324,7 @@ def confirm_more(
|
||||
title: str,
|
||||
button: str,
|
||||
items: Iterable[tuple[int, str]],
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm long content with the possibility to go back from any page.
|
||||
Meant to be used with confirm_with_info."""
|
||||
|
||||
@ -278,7 +334,7 @@ def confirm_coinjoin(
|
||||
*,
|
||||
max_rounds: str,
|
||||
max_feerate: str,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Confirm coinjoin authorization."""
|
||||
|
||||
|
||||
@ -289,7 +345,7 @@ def request_pin(
|
||||
subprompt: str,
|
||||
allow_cancel: bool = True,
|
||||
wrong_pin: bool = False,
|
||||
) -> str | object:
|
||||
) -> LayoutObj[str | UiResult]:
|
||||
"""Request pin on device."""
|
||||
|
||||
|
||||
@ -298,7 +354,7 @@ def request_passphrase(
|
||||
*,
|
||||
prompt: str,
|
||||
max_len: int,
|
||||
) -> str | object:
|
||||
) -> LayoutObj[str | UiResult]:
|
||||
"""Passphrase input keyboard."""
|
||||
|
||||
|
||||
@ -308,7 +364,7 @@ def request_bip39(
|
||||
prompt: str,
|
||||
prefill_word: str,
|
||||
can_go_back: bool,
|
||||
) -> str:
|
||||
) -> LayoutObj[str]:
|
||||
"""BIP39 word input keyboard."""
|
||||
|
||||
|
||||
@ -318,7 +374,7 @@ def request_slip39(
|
||||
prompt: str,
|
||||
prefill_word: str,
|
||||
can_go_back: bool,
|
||||
) -> str:
|
||||
) -> LayoutObj[str]:
|
||||
"""SLIP39 word input keyboard."""
|
||||
|
||||
|
||||
@ -328,7 +384,7 @@ def select_word(
|
||||
title: str,
|
||||
description: str,
|
||||
words: Iterable[str],
|
||||
) -> int:
|
||||
) -> LayoutObj[int]:
|
||||
"""Select mnemonic word from three possibilities - seed check after backup. The
|
||||
iterable must be of exact size. Returns index in range `0..3`."""
|
||||
|
||||
@ -338,7 +394,7 @@ def show_share_words(
|
||||
*,
|
||||
title: str,
|
||||
pages: Iterable[str],
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show mnemonic for backup. Expects the words pre-divided into individual pages."""
|
||||
|
||||
|
||||
@ -350,7 +406,7 @@ def request_number(
|
||||
min_count: int,
|
||||
max_count: int,
|
||||
description: Callable[[int], str] | None = None,
|
||||
) -> object:
|
||||
) -> LayoutObj[tuple[UiResult, int]]:
|
||||
"""Number input with + and - buttons, description, and info button."""
|
||||
|
||||
|
||||
@ -361,7 +417,7 @@ def show_checklist(
|
||||
items: Iterable[str],
|
||||
active: int,
|
||||
button: str,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Checklist of backup steps. Active index is highlighted, previous items have check
|
||||
mark next to them."""
|
||||
|
||||
@ -374,7 +430,7 @@ def confirm_recovery(
|
||||
button: str,
|
||||
dry_run: bool,
|
||||
info_button: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Device recovery homescreen."""
|
||||
|
||||
|
||||
@ -382,7 +438,7 @@ def confirm_recovery(
|
||||
def select_word_count(
|
||||
*,
|
||||
dry_run: bool,
|
||||
) -> int | str: # TT returns int
|
||||
) -> LayoutObj[int | str]: # TT returns int
|
||||
"""Select mnemonic word count from (12, 18, 20, 24, 33)."""
|
||||
|
||||
|
||||
@ -390,7 +446,7 @@ def select_word_count(
|
||||
def show_group_share_success(
|
||||
*,
|
||||
lines: Iterable[str]
|
||||
) -> int:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Shown after successfully finishing a group."""
|
||||
|
||||
|
||||
@ -398,7 +454,7 @@ def show_group_share_success(
|
||||
def show_remaining_shares(
|
||||
*,
|
||||
pages: Iterable[tuple[str, str]],
|
||||
) -> int:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Shows SLIP39 state after info button is pressed on `confirm_recovery`."""
|
||||
|
||||
|
||||
@ -408,7 +464,7 @@ def show_progress(
|
||||
title: str,
|
||||
indeterminate: bool = False,
|
||||
description: str = "",
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show progress loader. Please note that the number of lines reserved on screen for
|
||||
description is determined at construction time. If you want multiline descriptions
|
||||
make sure the initial description has at least that amount of lines."""
|
||||
@ -421,7 +477,7 @@ def show_progress_coinjoin(
|
||||
indeterminate: bool = False,
|
||||
time_ms: int = 0,
|
||||
skip_first_paint: bool = False,
|
||||
) -> object:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Show progress loader for coinjoin. Returns CANCELLED after a specified time when
|
||||
time_ms timeout is passed."""
|
||||
|
||||
@ -434,7 +490,7 @@ def show_homescreen(
|
||||
notification: str | None,
|
||||
notification_level: int = 0,
|
||||
skip_first_paint: bool,
|
||||
) -> CANCELLED:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Idle homescreen."""
|
||||
|
||||
|
||||
@ -445,7 +501,7 @@ def show_lockscreen(
|
||||
bootscreen: bool,
|
||||
skip_first_paint: bool,
|
||||
coinjoin_authorized: bool = False,
|
||||
) -> CANCELLED:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Homescreen for locked device."""
|
||||
|
||||
|
||||
@ -454,16 +510,16 @@ def confirm_firmware_update(
|
||||
*,
|
||||
description: str,
|
||||
fingerprint: str,
|
||||
) -> None:
|
||||
) -> LayoutObj[UiResult]:
|
||||
"""Ask whether to update firmware, optionally show fingerprint. Shared with bootloader."""
|
||||
|
||||
|
||||
# rust/src/ui/model_mercury/layout.rs
|
||||
def show_wait_text(/, message: str) -> None:
|
||||
def show_wait_text(message: str, /) -> LayoutObj[None]:
|
||||
"""Show single-line text in the middle of the screen."""
|
||||
CONFIRMED: object
|
||||
CANCELLED: object
|
||||
INFO: object
|
||||
CONFIRMED: UiResult
|
||||
CANCELLED: UiResult
|
||||
INFO: UiResult
|
||||
|
||||
|
||||
# rust/src/ui/model_tr/layout.rs
|
||||
|
@ -655,11 +655,16 @@ async def confirm_output(
|
||||
return
|
||||
|
||||
|
||||
async def confirm_payment_request(
|
||||
async def should_show_payment_request_details(
|
||||
recipient_name: str,
|
||||
amount: str,
|
||||
memos: list[str],
|
||||
) -> bool:
|
||||
"""Return True if the user wants to show payment request details (they click a
|
||||
special button) and False when the user wants to continue without showing details.
|
||||
|
||||
Raises ActionCancelled if the user cancels.
|
||||
"""
|
||||
result = await interact(
|
||||
RustLayout(
|
||||
trezorui2.confirm_with_info(
|
||||
@ -674,12 +679,10 @@ async def confirm_payment_request(
|
||||
ButtonRequestType.ConfirmOutput,
|
||||
)
|
||||
|
||||
# When user pressed INFO, returning False, which gets processed in higher function
|
||||
# to differentiate it from CONFIRMED. Raising otherwise.
|
||||
if result is CONFIRMED:
|
||||
return True
|
||||
elif result is INFO:
|
||||
return False
|
||||
elif result is INFO:
|
||||
return True
|
||||
else:
|
||||
raise ActionCancelled
|
||||
|
||||
|
@ -376,6 +376,7 @@ def test_signmessage_pagination(client: Client, message: str):
|
||||
|
||||
@pytest.mark.skip_t1b1
|
||||
@pytest.mark.skip_t2b1(reason="Different screen size")
|
||||
@pytest.mark.skip_t3t1(reason="Different fonts")
|
||||
def test_signmessage_pagination_trailing_newline(client: Client):
|
||||
message = "THIS\nMUST\nNOT\nBE\nPAGINATED\n"
|
||||
# The trailing newline must not cause a new paginated screen to appear.
|
||||
|
@ -950,7 +950,7 @@ class InputFlowLockTimeDatetime(InputFlowBase):
|
||||
def assert_func(self, debug: DebugLink, br: messages.ButtonRequest) -> None:
|
||||
layout_text = get_text_possible_pagination(debug, br)
|
||||
TR.assert_in(layout_text, "bitcoin__locktime_set_to")
|
||||
assert self.lock_time_str in layout_text
|
||||
assert self.lock_time_str.replace(' ', '') in layout_text.replace(' ', '')
|
||||
|
||||
def input_flow_tt(self) -> BRGeneratorType:
|
||||
yield from lock_time_input_flow_tt(self.debug, self.assert_func)
|
||||
|
Loading…
Reference in New Issue
Block a user