mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-16 17:42:02 +00:00
feat(core/rust/ui): bitcoin layouts
[no changelog]
This commit is contained in:
parent
dd9a7d30e5
commit
c9ca7cd544
BIN
core/assets/error.png
Normal file
BIN
core/assets/error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
core/assets/success.png
Normal file
BIN
core/assets/success.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
core/assets/warn.png
Normal file
BIN
core/assets/warn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
@ -258,6 +258,7 @@ fn generate_trezorhal_bindings() {
|
|||||||
.allowlist_function("display_bar")
|
.allowlist_function("display_bar")
|
||||||
.allowlist_function("display_bar_radius")
|
.allowlist_function("display_bar_radius")
|
||||||
.allowlist_function("display_icon")
|
.allowlist_function("display_icon")
|
||||||
|
.allowlist_function("display_image")
|
||||||
.allowlist_function("display_toif_info")
|
.allowlist_function("display_toif_info")
|
||||||
.allowlist_function("display_loader")
|
.allowlist_function("display_loader")
|
||||||
.allowlist_function("display_pixeldata")
|
.allowlist_function("display_pixeldata")
|
||||||
|
@ -17,7 +17,18 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_CANCELLED;
|
MP_QSTR_CANCELLED;
|
||||||
MP_QSTR_INFO;
|
MP_QSTR_INFO;
|
||||||
MP_QSTR_confirm_action;
|
MP_QSTR_confirm_action;
|
||||||
|
MP_QSTR_confirm_blob;
|
||||||
|
MP_QSTR_confirm_coinjoin;
|
||||||
|
MP_QSTR_confirm_joint_total;
|
||||||
|
MP_QSTR_confirm_modify_fee;
|
||||||
|
MP_QSTR_confirm_modify_output;
|
||||||
|
MP_QSTR_confirm_output;
|
||||||
|
MP_QSTR_confirm_payment_request;
|
||||||
MP_QSTR_confirm_text;
|
MP_QSTR_confirm_text;
|
||||||
|
MP_QSTR_confirm_total;
|
||||||
|
MP_QSTR_show_qr;
|
||||||
|
MP_QSTR_show_success;
|
||||||
|
MP_QSTR_show_warning;
|
||||||
MP_QSTR_request_pin;
|
MP_QSTR_request_pin;
|
||||||
MP_QSTR_request_passphrase;
|
MP_QSTR_request_passphrase;
|
||||||
MP_QSTR_request_bip39;
|
MP_QSTR_request_bip39;
|
||||||
@ -33,14 +44,28 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_page_count;
|
MP_QSTR_page_count;
|
||||||
|
|
||||||
MP_QSTR_title;
|
MP_QSTR_title;
|
||||||
|
MP_QSTR_subtitle;
|
||||||
MP_QSTR_action;
|
MP_QSTR_action;
|
||||||
MP_QSTR_description;
|
MP_QSTR_description;
|
||||||
|
MP_QSTR_extra;
|
||||||
MP_QSTR_verb;
|
MP_QSTR_verb;
|
||||||
MP_QSTR_verb_cancel;
|
MP_QSTR_verb_cancel;
|
||||||
|
MP_QSTR_hold;
|
||||||
MP_QSTR_reverse;
|
MP_QSTR_reverse;
|
||||||
MP_QSTR_prompt;
|
MP_QSTR_prompt;
|
||||||
MP_QSTR_subprompt;
|
MP_QSTR_subprompt;
|
||||||
MP_QSTR_warning;
|
MP_QSTR_warning;
|
||||||
MP_QSTR_allow_cancel;
|
MP_QSTR_allow_cancel;
|
||||||
MP_QSTR_max_len;
|
MP_QSTR_max_len;
|
||||||
|
MP_QSTR_amount_change;
|
||||||
|
MP_QSTR_amount_new;
|
||||||
|
MP_QSTR_ask_pagination;
|
||||||
|
MP_QSTR_case_sensitive;
|
||||||
|
MP_QSTR_coin_name;
|
||||||
|
MP_QSTR_max_feerate;
|
||||||
|
MP_QSTR_max_rounds;
|
||||||
|
MP_QSTR_spending_amount;
|
||||||
|
MP_QSTR_total_amount;
|
||||||
|
MP_QSTR_total_fee_new;
|
||||||
|
MP_QSTR_user_fee_change;
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,12 @@ impl AsMut<[u8]> for BufferMut {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct StrBuffer(Buffer);
|
pub struct StrBuffer(Buffer);
|
||||||
|
|
||||||
|
impl StrBuffer {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self::from("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<Obj> for StrBuffer {
|
impl TryFrom<Obj> for StrBuffer {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
|
@ -100,6 +100,18 @@ impl Map {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_or<T>(&self, index: impl Into<Obj>, default: T) -> Result<T, Error>
|
||||||
|
where
|
||||||
|
T: TryFrom<Obj, Error = Error>,
|
||||||
|
{
|
||||||
|
let res = self.get(index);
|
||||||
|
match res {
|
||||||
|
Ok(obj) => obj.try_into(),
|
||||||
|
Err(Error::KeyError(_)) => Ok(default),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, index: impl Into<Obj>, value: impl Into<Obj>) -> Result<(), Error> {
|
pub fn set(&mut self, index: impl Into<Obj>, value: impl Into<Obj>) -> Result<(), Error> {
|
||||||
self.set_obj(index.into(), value.into())
|
self.set_obj(index.into(), value.into())
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,10 @@ pub fn icon(x: i32, y: i32, w: i32, h: i32, data: &[u8], fgcolor: u16, bgcolor:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn image(x: i32, y: i32, w: i32, h: i32, data: &[u8]) {
|
||||||
|
unsafe { ffi::display_image(x, y, w, h, data.as_ptr() as _, data.len() as _) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toif_info(data: &[u8]) -> Result<ToifInfo, ()> {
|
pub fn toif_info(data: &[u8]) -> Result<ToifInfo, ()> {
|
||||||
let mut width: cty::uint16_t = 0;
|
let mut width: cty::uint16_t = 0;
|
||||||
let mut height: cty::uint16_t = 0;
|
let mut height: cty::uint16_t = 0;
|
||||||
|
@ -3,6 +3,7 @@ pub mod common;
|
|||||||
#[cfg(feature = "ui")]
|
#[cfg(feature = "ui")]
|
||||||
pub mod display;
|
pub mod display;
|
||||||
mod ffi;
|
mod ffi;
|
||||||
|
pub mod qr;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
#[cfg(feature = "model_tr")]
|
#[cfg(feature = "model_tr")]
|
||||||
pub mod rgb_led;
|
pub mod rgb_led;
|
||||||
|
75
core/embed/rust/src/trezorhal/qr.rs
Normal file
75
core/embed/rust/src/trezorhal/qr.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
use cstr_core::CStr;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
fn display_qrcode(
|
||||||
|
x: cty::c_int,
|
||||||
|
y: cty::c_int,
|
||||||
|
data: *const cty::uint16_t,
|
||||||
|
scale: cty::uint8_t,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NVERSIONS: usize = 10; // range of versions (=capacities) that we support
|
||||||
|
const QR_WIDTHS: [u32; NVERSIONS] = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57];
|
||||||
|
const THRESHOLDS_BINARY: [usize; NVERSIONS] = [14, 26, 42, 62, 84, 106, 122, 152, 180, 213];
|
||||||
|
const THRESHOLDS_ALPHANUM: [usize; NVERSIONS] = [20, 38, 61, 90, 122, 154, 178, 221, 262, 311];
|
||||||
|
const ALPHANUM: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $*+-./:";
|
||||||
|
|
||||||
|
const MAX_DATA: usize = THRESHOLDS_ALPHANUM[THRESHOLDS_ALPHANUM.len() - 1] + 1; //FIXME
|
||||||
|
|
||||||
|
fn is_alphanum_only(data: &str) -> bool {
|
||||||
|
data.chars().all(|c| ALPHANUM.contains(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qr_version_index(data: &str, thresholds: &[usize]) -> Option<usize> {
|
||||||
|
for (i, threshold) in thresholds.iter().enumerate() {
|
||||||
|
if data.len() <= *threshold {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_qrcode(
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
data: &str,
|
||||||
|
max_size: u32,
|
||||||
|
case_sensitive: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let data_len = data.len();
|
||||||
|
let version_idx;
|
||||||
|
let mut buffer = [0u8; MAX_DATA];
|
||||||
|
assert!(data_len < buffer.len());
|
||||||
|
buffer.as_mut_slice()[..data_len].copy_from_slice(data.as_bytes());
|
||||||
|
|
||||||
|
if case_sensitive && !is_alphanum_only(data) {
|
||||||
|
version_idx = match qr_version_index(data, &THRESHOLDS_BINARY) {
|
||||||
|
Some(idx) => idx,
|
||||||
|
_ => return Err(Error::OutOfRange),
|
||||||
|
}
|
||||||
|
} else if let Some(idx) = qr_version_index(data, &THRESHOLDS_ALPHANUM) {
|
||||||
|
version_idx = idx;
|
||||||
|
if data_len > THRESHOLDS_BINARY[idx] {
|
||||||
|
for c in buffer.iter_mut() {
|
||||||
|
c.make_ascii_uppercase()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Err(Error::OutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = QR_WIDTHS[version_idx];
|
||||||
|
let scale = max_size / size;
|
||||||
|
assert!((1..=10).contains(&scale));
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
buffer[data_len] = 0u8;
|
||||||
|
let cstr = CStr::from_bytes_with_nul_unchecked(&buffer[..data_len + 1]);
|
||||||
|
|
||||||
|
display_qrcode(x, y, cstr.as_ptr() as _, scale as u8);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
ui::{
|
ui::{
|
||||||
component::{maybe::PaintOverlapping, Map},
|
component::{maybe::PaintOverlapping, Map},
|
||||||
display::Color,
|
display::Color,
|
||||||
geometry::Rect,
|
geometry::{Offset, Rect},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,6 +210,60 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M, T, U, V> Component for (T, U, V)
|
||||||
|
where
|
||||||
|
T: Component<Msg = M>,
|
||||||
|
U: Component<Msg = M>,
|
||||||
|
V: Component<Msg = M>,
|
||||||
|
{
|
||||||
|
type Msg = M;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.0
|
||||||
|
.place(bounds)
|
||||||
|
.union(self.1.place(bounds))
|
||||||
|
.union(self.2.place(bounds))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
self.0
|
||||||
|
.event(ctx, event)
|
||||||
|
.or_else(|| self.1.event(ctx, event))
|
||||||
|
.or_else(|| self.2.event(ctx, event))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.0.paint();
|
||||||
|
self.1.paint();
|
||||||
|
self.2.paint();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.0.bounds(sink);
|
||||||
|
self.1.bounds(sink);
|
||||||
|
self.2.bounds(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T, U, V> crate::trace::Trace for (T, U, V)
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
T: crate::trace::Trace,
|
||||||
|
U: Component,
|
||||||
|
U: crate::trace::Trace,
|
||||||
|
V: Component,
|
||||||
|
V: crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||||
|
d.open("Tuple");
|
||||||
|
d.field("0", &self.0);
|
||||||
|
d.field("1", &self.1);
|
||||||
|
d.field("2", &self.2);
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Component for Option<T>
|
impl<T> Component for Option<T>
|
||||||
where
|
where
|
||||||
T: Component,
|
T: Component,
|
||||||
|
42
core/embed/rust/src/ui/component/image.rs
Normal file
42
core/embed/rust/src/ui/component/image.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Component, Event, EventCtx, Never},
|
||||||
|
display,
|
||||||
|
geometry::Rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Image {
|
||||||
|
image: &'static [u8],
|
||||||
|
area: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub fn new(image: &'static [u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
image,
|
||||||
|
area: Rect::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Image {
|
||||||
|
type Msg = Never;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.area = bounds;
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
display::image(self.area.center(), self.image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
if let Some((size, _)) = display::toif_info(self.image) {
|
||||||
|
sink(Rect::from_center_and_size(self.area.center(), size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -92,3 +92,13 @@ where
|
|||||||
sink(self.area)
|
sink(self.area)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for Label<T>
|
||||||
|
where
|
||||||
|
T: Deref<Target = str>,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.string(&self.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod empty;
|
pub mod empty;
|
||||||
|
pub mod image;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod maybe;
|
pub mod maybe;
|
||||||
@ -13,12 +14,13 @@ pub mod text;
|
|||||||
|
|
||||||
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken};
|
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken};
|
||||||
pub use empty::Empty;
|
pub use empty::Empty;
|
||||||
|
pub use image::Image;
|
||||||
pub use label::{Label, LabelStyle};
|
pub use label::{Label, LabelStyle};
|
||||||
pub use map::Map;
|
pub use map::Map;
|
||||||
pub use maybe::Maybe;
|
pub use maybe::Maybe;
|
||||||
pub use pad::Pad;
|
pub use pad::Pad;
|
||||||
pub use paginated::{PageMsg, Paginate};
|
pub use paginated::{PageMsg, Paginate};
|
||||||
pub use painter::Painter;
|
pub use painter::{qrcode_painter, Painter};
|
||||||
pub use placed::GridPlaced;
|
pub use placed::GridPlaced;
|
||||||
pub use text::{
|
pub use text::{
|
||||||
formatted::FormattedText,
|
formatted::FormattedText,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, Event, EventCtx, Never},
|
component::{Component, Event, EventCtx, Never},
|
||||||
|
display,
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,4 +36,31 @@ where
|
|||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
(self.func)(self.area);
|
(self.func)(self.area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
sink(self.area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<F> crate::trace::Trace for Painter<F> {
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.string("Painter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn qrcode_painter<T>(data: T, max_size: u32, case_sensitive: bool) -> Painter<impl FnMut(Rect)>
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
// Ignore errors as we currently can't propagate them out of paint().
|
||||||
|
let f = move |area: Rect| {
|
||||||
|
display::qrcode(area.center(), data.as_ref(), max_size, case_sensitive).unwrap_or(())
|
||||||
|
};
|
||||||
|
Painter::new(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn image_painter(image: &'static [u8]) -> Painter<impl FnMut(Rect)> {
|
||||||
|
let f = move |area: Rect| display::image(area.center(), image);
|
||||||
|
Painter::new(f)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use super::constant;
|
use super::constant;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::Error,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
trezorhal::{display, time},
|
trezorhal::{display, qr, time},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::geometry::{Offset, Point, Rect};
|
use super::geometry::{Offset, Point, Rect};
|
||||||
@ -91,6 +92,34 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn image(center: Point, data: &[u8]) {
|
||||||
|
let toif_info = display::toif_info(data).unwrap();
|
||||||
|
assert!(!toif_info.grayscale);
|
||||||
|
|
||||||
|
let r = Rect::from_center_and_size(
|
||||||
|
center,
|
||||||
|
Offset::new(toif_info.width.into(), toif_info.height.into()),
|
||||||
|
);
|
||||||
|
display::image(
|
||||||
|
r.x0,
|
||||||
|
r.y0,
|
||||||
|
r.width(),
|
||||||
|
r.height(),
|
||||||
|
&data[12..], // Skip TOIF header.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toif_info(data: &[u8]) -> Option<(Offset, bool)> {
|
||||||
|
if let Ok(info) = display::toif_info(data) {
|
||||||
|
Some((
|
||||||
|
Offset::new(info.width.into(), info.height.into()),
|
||||||
|
info.grayscale,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Used on T1 only.
|
// Used on T1 only.
|
||||||
pub fn rect_fill_rounded1(r: Rect, fg_color: Color, bg_color: Color) {
|
pub fn rect_fill_rounded1(r: Rect, fg_color: Color, bg_color: Color) {
|
||||||
display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into());
|
display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into());
|
||||||
@ -151,6 +180,10 @@ pub fn loader_indeterminate(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
pub fn text(baseline: Point, text: &str, font: Font, fg_color: Color, bg_color: Color) {
|
||||||
display::text(
|
display::text(
|
||||||
baseline.x,
|
baseline.x,
|
||||||
|
@ -27,8 +27,7 @@ use crate::ui::event::ButtonEvent;
|
|||||||
use crate::ui::event::TouchEvent;
|
use crate::ui::event::TouchEvent;
|
||||||
|
|
||||||
/// Conversion trait implemented by components that know how to convert their
|
/// Conversion trait implemented by components that know how to convert their
|
||||||
/// message values into MicroPython `Obj`s. We can automatically implement
|
/// message values into MicroPython `Obj`s.
|
||||||
/// `ComponentMsgObj` for components whose message types implement `TryInto`.
|
|
||||||
pub trait ComponentMsgObj: Component {
|
pub trait ComponentMsgObj: Component {
|
||||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error>;
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error>;
|
||||||
}
|
}
|
||||||
|
@ -340,28 +340,115 @@ pub struct ButtonStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Button<T> {
|
impl<T> Button<T> {
|
||||||
pub fn left_right<F0, F1, R>(
|
pub fn cancel_confirm(
|
||||||
left: Button<T>,
|
left: Button<T>,
|
||||||
left_map: F0,
|
|
||||||
right: Button<T>,
|
right: Button<T>,
|
||||||
right_map: F1,
|
right_size_factor: usize,
|
||||||
) -> (Map<GridPlaced<Self>, F0>, Map<GridPlaced<Self>, F1>)
|
) -> CancelConfirm<
|
||||||
|
T,
|
||||||
|
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||||
|
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||||
|
>
|
||||||
where
|
where
|
||||||
F0: Fn(ButtonMsg) -> Option<R>,
|
|
||||||
F1: Fn(ButtonMsg) -> Option<R>,
|
|
||||||
T: AsRef<str>,
|
T: AsRef<str>,
|
||||||
{
|
{
|
||||||
|
let columns = 1 + right_size_factor;
|
||||||
(
|
(
|
||||||
GridPlaced::new(left)
|
GridPlaced::new(left)
|
||||||
.with_grid(1, 3)
|
.with_grid(1, columns)
|
||||||
.with_spacing(theme::BUTTON_SPACING)
|
.with_spacing(theme::BUTTON_SPACING)
|
||||||
.with_row_col(0, 0)
|
.with_row_col(0, 0)
|
||||||
.map(left_map),
|
.map(|msg| {
|
||||||
|
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Cancelled)
|
||||||
|
}),
|
||||||
GridPlaced::new(right)
|
GridPlaced::new(right)
|
||||||
.with_grid(1, 3)
|
.with_grid(1, columns)
|
||||||
.with_spacing(theme::BUTTON_SPACING)
|
.with_spacing(theme::BUTTON_SPACING)
|
||||||
.with_from_to((0, 1), (0, 2))
|
.with_from_to((0, 1), (0, right_size_factor))
|
||||||
.map(right_map),
|
.map(|msg| {
|
||||||
|
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_confirm_text(
|
||||||
|
left: Option<T>,
|
||||||
|
right: T,
|
||||||
|
) -> CancelConfirm<
|
||||||
|
T,
|
||||||
|
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||||
|
impl Fn(ButtonMsg) -> Option<CancelConfirmMsg>,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
let (left, right_size_factor) = if let Some(verb) = left {
|
||||||
|
(Button::with_text(verb), 1)
|
||||||
|
} else {
|
||||||
|
(Button::with_icon(theme::ICON_CANCEL), 2)
|
||||||
|
};
|
||||||
|
let right = Button::with_text(right).styled(theme::button_confirm());
|
||||||
|
|
||||||
|
Self::cancel_confirm(left, right, right_size_factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel_info_confirm(
|
||||||
|
confirm: T,
|
||||||
|
info: T,
|
||||||
|
) -> 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());
|
||||||
|
let top = Button::with_text(info);
|
||||||
|
let left = Button::with_icon(theme::ICON_CANCEL);
|
||||||
|
(
|
||||||
|
GridPlaced::new(left)
|
||||||
|
.with_grid(2, 3)
|
||||||
|
.with_spacing(theme::BUTTON_SPACING)
|
||||||
|
.with_row_col(1, 0)
|
||||||
|
.map(|msg| {
|
||||||
|
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Cancelled)
|
||||||
|
}),
|
||||||
|
GridPlaced::new(top)
|
||||||
|
.with_grid(2, 3)
|
||||||
|
.with_spacing(theme::BUTTON_SPACING)
|
||||||
|
.with_from_to((0, 0), (0, 2))
|
||||||
|
.map(|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Info)),
|
||||||
|
GridPlaced::new(right)
|
||||||
|
.with_grid(2, 3)
|
||||||
|
.with_spacing(theme::BUTTON_SPACING)
|
||||||
|
.with_from_to((1, 1), (1, 2))
|
||||||
|
.map(|msg| {
|
||||||
|
(matches!(msg, ButtonMsg::Clicked)).then(|| CancelInfoConfirmMsg::Confirmed)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CancelConfirm<T, F0, F1> = (
|
||||||
|
Map<GridPlaced<Button<T>>, F0>,
|
||||||
|
Map<GridPlaced<Button<T>>, F1>,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub enum CancelConfirmMsg {
|
||||||
|
Cancelled,
|
||||||
|
Confirmed,
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancelInfoConfirm<T, F0, F1, F2> = (
|
||||||
|
Map<GridPlaced<Button<T>>, F0>,
|
||||||
|
Map<GridPlaced<Button<T>>, F1>,
|
||||||
|
Map<GridPlaced<Button<T>>, F2>,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub enum CancelInfoConfirmMsg {
|
||||||
|
Cancelled,
|
||||||
|
Info,
|
||||||
|
Confirmed,
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use core::ops::Deref;
|
||||||
|
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Child, Component, Event, EventCtx},
|
component::{Child, Component, Event, EventCtx, Image, Label, Never},
|
||||||
geometry::{Grid, Rect},
|
geometry::{Grid, Insets, Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{theme, Button};
|
use super::{theme, Button};
|
||||||
@ -95,3 +97,94 @@ where
|
|||||||
t.close();
|
t.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct IconDialog<T, U> {
|
||||||
|
image: Child<Image>,
|
||||||
|
title: Label<T>,
|
||||||
|
description: Option<Label<T>>,
|
||||||
|
controls: Child<U>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> IconDialog<T, U>
|
||||||
|
where
|
||||||
|
T: Deref<Target = str>,
|
||||||
|
U: Component,
|
||||||
|
{
|
||||||
|
pub fn new(icon: &'static [u8], title: T, controls: U) -> Self {
|
||||||
|
Self {
|
||||||
|
image: Child::new(Image::new(icon)),
|
||||||
|
title: Label::centered(title, theme::label_warning()),
|
||||||
|
description: None,
|
||||||
|
controls: Child::new(controls),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_description(mut self, description: T) -> Self {
|
||||||
|
self.description = Some(Label::centered(description, theme::label_warning_value()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ICON_AREA_HEIGHT: i32 = 64;
|
||||||
|
pub const DESCRIPTION_SPACE: i32 = 13;
|
||||||
|
pub const VALUE_SPACE: i32 = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Component for IconDialog<T, U>
|
||||||
|
where
|
||||||
|
T: Deref<Target = str>,
|
||||||
|
U: Component,
|
||||||
|
{
|
||||||
|
type Msg = DialogMsg<Never, U::Msg>;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let bounds = bounds.inset(theme::borders());
|
||||||
|
let (content, buttons) = bounds.split_bottom(Button::<&str>::HEIGHT);
|
||||||
|
let (image, content) = content.split_top(Self::ICON_AREA_HEIGHT);
|
||||||
|
let content = content.inset(Insets::top(Self::DESCRIPTION_SPACE));
|
||||||
|
let (title, content) = content.split_top(self.title.size().y);
|
||||||
|
let content = content.inset(Insets::top(Self::VALUE_SPACE));
|
||||||
|
|
||||||
|
self.image.place(image);
|
||||||
|
self.title.place(title);
|
||||||
|
self.description.place(content);
|
||||||
|
self.controls.place(buttons);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
self.title.event(ctx, event);
|
||||||
|
self.description.event(ctx, event);
|
||||||
|
self.controls.event(ctx, event).map(Self::Msg::Controls)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.image.paint();
|
||||||
|
self.title.paint();
|
||||||
|
self.description.paint();
|
||||||
|
self.controls.paint();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.image.bounds(sink);
|
||||||
|
self.title.bounds(sink);
|
||||||
|
self.description.bounds(sink);
|
||||||
|
self.controls.bounds(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T, U> crate::trace::Trace for IconDialog<T, U>
|
||||||
|
where
|
||||||
|
T: Deref<Target = str>,
|
||||||
|
U: crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("IconDialog");
|
||||||
|
t.field("title", &self.title);
|
||||||
|
if let Some(ref description) = self.description {
|
||||||
|
t.field("description", description);
|
||||||
|
}
|
||||||
|
t.field("controls", &self.controls);
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ use crate::ui::{
|
|||||||
|
|
||||||
pub struct Frame<T, U> {
|
pub struct Frame<T, U> {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
|
border: Insets,
|
||||||
title: U,
|
title: U,
|
||||||
content: Child<T>,
|
content: Child<T>,
|
||||||
}
|
}
|
||||||
@ -20,10 +21,16 @@ where
|
|||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
area: Rect::zero(),
|
area: Rect::zero(),
|
||||||
|
border: theme::borders_scroll(),
|
||||||
content: Child::new(content),
|
content: Child::new(content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_border(mut self, border: Insets) -> Self {
|
||||||
|
self.border = border;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner(&self) -> &T {
|
pub fn inner(&self) -> &T {
|
||||||
self.content.inner()
|
self.content.inner()
|
||||||
}
|
}
|
||||||
@ -37,11 +44,10 @@ where
|
|||||||
type Msg = T::Msg;
|
type Msg = T::Msg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
// Same as PageLayout::BUTTON_SPACE.
|
const TITLE_SPACE: i32 = theme::BUTTON_SPACING;
|
||||||
const TITLE_SPACE: i32 = 6;
|
|
||||||
|
|
||||||
let (title_area, content_area) = bounds
|
let (title_area, content_area) = bounds
|
||||||
.inset(theme::borders_scroll())
|
.inset(self.border)
|
||||||
.split_top(theme::FONT_BOLD.text_height());
|
.split_top(theme::FONT_BOLD.text_height());
|
||||||
let title_area = title_area.inset(Insets::left(theme::CONTENT_BORDER));
|
let title_area = title_area.inset(Insets::left(theme::CONTENT_BORDER));
|
||||||
let content_area = content_area.inset(Insets::top(TITLE_SPACE));
|
let content_area = content_area.inset(Insets::top(TITLE_SPACE));
|
||||||
|
@ -8,8 +8,11 @@ mod page;
|
|||||||
mod scroll;
|
mod scroll;
|
||||||
mod swipe;
|
mod swipe;
|
||||||
|
|
||||||
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet};
|
pub use button::{
|
||||||
pub use dialog::{Dialog, DialogLayout, DialogMsg};
|
Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet, CancelConfirmMsg,
|
||||||
|
CancelInfoConfirmMsg,
|
||||||
|
};
|
||||||
|
pub use dialog::{Dialog, DialogLayout, DialogMsg, IconDialog};
|
||||||
pub use frame::Frame;
|
pub use frame::Frame;
|
||||||
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||||
pub use keyboard::{
|
pub use keyboard::{
|
||||||
|
@ -8,7 +8,7 @@ use crate::ui::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
hold_to_confirm::{handle_hold_event, CancelHold, CancelHoldMsg},
|
hold_to_confirm::{handle_hold_event, CancelHold, CancelHoldMsg},
|
||||||
theme, Button, Loader, ScrollBar, Swipe, SwipeDirection,
|
theme, Button, CancelConfirmMsg, Loader, ScrollBar, Swipe, SwipeDirection,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SwipePage<T, U> {
|
pub struct SwipePage<T, U> {
|
||||||
@ -19,6 +19,7 @@ pub struct SwipePage<T, U> {
|
|||||||
scrollbar: ScrollBar,
|
scrollbar: ScrollBar,
|
||||||
hint: Label<&'static str>,
|
hint: Label<&'static str>,
|
||||||
fade: Option<i32>,
|
fade: Option<i32>,
|
||||||
|
button_rows: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> SwipePage<T, U>
|
impl<T, U> SwipePage<T, U>
|
||||||
@ -36,9 +37,15 @@ where
|
|||||||
pad: Pad::with_background(background),
|
pad: Pad::with_background(background),
|
||||||
hint: Label::centered("SWIPE TO CONTINUE", theme::label_page_hint()),
|
hint: Label::centered("SWIPE TO CONTINUE", theme::label_page_hint()),
|
||||||
fade: None,
|
fade: None,
|
||||||
|
button_rows: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_button_rows(mut self, rows: usize) -> Self {
|
||||||
|
self.button_rows = rows as i32;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_swipe(&mut self) {
|
fn setup_swipe(&mut self) {
|
||||||
self.swipe.allow_up = self.scrollbar.has_next_page();
|
self.swipe.allow_up = self.scrollbar.has_next_page();
|
||||||
self.swipe.allow_down = self.scrollbar.has_previous_page();
|
self.swipe.allow_down = self.scrollbar.has_previous_page();
|
||||||
@ -69,7 +76,7 @@ where
|
|||||||
type Msg = PageMsg<T::Msg, U::Msg>;
|
type Msg = PageMsg<T::Msg, U::Msg>;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
let layout = PageLayout::new(bounds);
|
let layout = PageLayout::new(bounds, self.button_rows);
|
||||||
self.pad.place(bounds);
|
self.pad.place(bounds);
|
||||||
self.swipe.place(bounds);
|
self.swipe.place(bounds);
|
||||||
self.hint.place(layout.hint);
|
self.hint.place(layout.hint);
|
||||||
@ -186,15 +193,16 @@ pub struct PageLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PageLayout {
|
impl PageLayout {
|
||||||
const BUTTON_SPACE: i32 = 6;
|
|
||||||
const SCROLLBAR_WIDTH: i32 = 10;
|
const SCROLLBAR_WIDTH: i32 = 10;
|
||||||
const SCROLLBAR_SPACE: i32 = 10;
|
const SCROLLBAR_SPACE: i32 = 10;
|
||||||
const HINT_OFF: i32 = 19;
|
const HINT_OFF: i32 = 19;
|
||||||
|
|
||||||
pub fn new(area: Rect) -> Self {
|
pub fn new(area: Rect, button_rows: i32) -> Self {
|
||||||
let (content, buttons) = area.split_bottom(Button::<&str>::HEIGHT);
|
let buttons_height = button_rows * Button::<&str>::HEIGHT
|
||||||
|
+ button_rows.saturating_sub(1) * theme::BUTTON_SPACING;
|
||||||
|
let (content, buttons) = area.split_bottom(buttons_height);
|
||||||
let (_, hint) = area.split_bottom(Self::HINT_OFF);
|
let (_, hint) = area.split_bottom(Self::HINT_OFF);
|
||||||
let (content, _space) = content.split_bottom(Self::BUTTON_SPACE);
|
let (content, _space) = content.split_bottom(theme::BUTTON_SPACING);
|
||||||
let (buttons, _space) = buttons.split_right(theme::CONTENT_BORDER);
|
let (buttons, _space) = buttons.split_right(theme::CONTENT_BORDER);
|
||||||
let (_space, content) = content.split_left(theme::CONTENT_BORDER);
|
let (_space, content) = content.split_left(theme::CONTENT_BORDER);
|
||||||
let (content_single_page, _space) = content.split_right(theme::CONTENT_BORDER);
|
let (content_single_page, _space) = content.split_right(theme::CONTENT_BORDER);
|
||||||
@ -236,7 +244,7 @@ where
|
|||||||
T: Paginate,
|
T: Paginate,
|
||||||
T: Component,
|
T: Component,
|
||||||
{
|
{
|
||||||
type Msg = PageMsg<T::Msg, bool>;
|
type Msg = PageMsg<T::Msg, CancelConfirmMsg>;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
self.inner.place(bounds);
|
self.inner.place(bounds);
|
||||||
@ -249,7 +257,7 @@ where
|
|||||||
let button_msg = match msg {
|
let button_msg = match msg {
|
||||||
Some(PageMsg::Content(c)) => return Some(PageMsg::Content(c)),
|
Some(PageMsg::Content(c)) => return Some(PageMsg::Content(c)),
|
||||||
Some(PageMsg::Controls(CancelHoldMsg::Cancelled)) => {
|
Some(PageMsg::Controls(CancelHoldMsg::Cancelled)) => {
|
||||||
return Some(PageMsg::Controls(false))
|
return Some(PageMsg::Controls(CancelConfirmMsg::Cancelled))
|
||||||
}
|
}
|
||||||
Some(PageMsg::Controls(CancelHoldMsg::HoldButton(b))) => Some(b),
|
Some(PageMsg::Controls(CancelHoldMsg::HoldButton(b))) => Some(b),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -262,7 +270,7 @@ where
|
|||||||
&mut self.inner.pad,
|
&mut self.inner.pad,
|
||||||
&mut self.inner.content,
|
&mut self.inner.content,
|
||||||
) {
|
) {
|
||||||
return Some(PageMsg::Controls(true));
|
return Some(PageMsg::Controls(CancelConfirmMsg::Confirmed));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,25 @@ use core::{convert::TryInto, ops::Deref};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
micropython::{buffer::StrBuffer, map::Map, module::Module, obj::Obj, qstr::Qstr, util},
|
micropython::{
|
||||||
|
buffer::StrBuffer,
|
||||||
|
iter::{Iter, IterBuf},
|
||||||
|
map::Map,
|
||||||
|
module::Module,
|
||||||
|
obj::Obj,
|
||||||
|
qstr::Qstr,
|
||||||
|
util,
|
||||||
|
},
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
|
self,
|
||||||
base::ComponentExt,
|
base::ComponentExt,
|
||||||
paginated::{PageMsg, Paginate},
|
paginated::{PageMsg, Paginate},
|
||||||
|
painter,
|
||||||
text::paragraphs::Paragraphs,
|
text::paragraphs::Paragraphs,
|
||||||
Component,
|
Component,
|
||||||
},
|
},
|
||||||
|
geometry,
|
||||||
layout::{
|
layout::{
|
||||||
obj::{ComponentMsgObj, LayoutObj},
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
result::{CANCELLED, CONFIRMED, INFO},
|
result::{CANCELLED, CONFIRMED, INFO},
|
||||||
@ -19,23 +30,61 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
component::{
|
component::{
|
||||||
Bip39Input, Button, ButtonMsg, Dialog, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
|
Bip39Input, Button, ButtonMsg, CancelConfirmMsg, CancelInfoConfirmMsg, Dialog, DialogMsg,
|
||||||
MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard,
|
Frame, HoldToConfirm, HoldToConfirmMsg, IconDialog, MnemonicInput, MnemonicKeyboard,
|
||||||
PassphraseKeyboardMsg, PinKeyboard, PinKeyboardMsg, Slip39Input, SwipeHoldPage, SwipePage,
|
MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg, PinKeyboard,
|
||||||
|
PinKeyboardMsg, Slip39Input, SwipeHoldPage, SwipePage,
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
impl TryFrom<CancelConfirmMsg> for Obj {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: CancelConfirmMsg) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
CancelConfirmMsg::Cancelled => Ok(CANCELLED.as_obj()),
|
||||||
|
CancelConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<CancelInfoConfirmMsg> for Obj {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: CancelInfoConfirmMsg) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
CancelInfoConfirmMsg::Cancelled => Ok(CANCELLED.as_obj()),
|
||||||
|
CancelInfoConfirmMsg::Info => Ok(INFO.as_obj()),
|
||||||
|
CancelInfoConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, U> ComponentMsgObj for Dialog<T, U>
|
impl<T, U> ComponentMsgObj for Dialog<T, U>
|
||||||
where
|
where
|
||||||
T: ComponentMsgObj,
|
T: ComponentMsgObj,
|
||||||
U: Component<Msg = bool>,
|
U: Component,
|
||||||
|
<U as Component>::Msg: TryInto<Obj, Error = Error>,
|
||||||
{
|
{
|
||||||
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 {
|
||||||
DialogMsg::Content(c) => Ok(self.inner().msg_try_into_obj(c)?),
|
DialogMsg::Content(c) => Ok(self.inner().msg_try_into_obj(c)?),
|
||||||
DialogMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
DialogMsg::Controls(msg) => msg.try_into(),
|
||||||
DialogMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> ComponentMsgObj for IconDialog<T, U>
|
||||||
|
where
|
||||||
|
T: Deref<Target = str>,
|
||||||
|
U: Component,
|
||||||
|
<U as Component>::Msg: TryInto<Obj, Error = Error>,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
DialogMsg::Controls(msg) => msg.try_into(),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,13 +154,13 @@ where
|
|||||||
impl<T, U> ComponentMsgObj for SwipePage<T, U>
|
impl<T, U> ComponentMsgObj for SwipePage<T, U>
|
||||||
where
|
where
|
||||||
T: Component + Paginate,
|
T: Component + Paginate,
|
||||||
U: Component<Msg = bool>,
|
U: Component,
|
||||||
|
<U as Component>::Msg: TryInto<Obj, Error = Error>,
|
||||||
{
|
{
|
||||||
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 {
|
||||||
PageMsg::Content(_) => Err(Error::TypeError),
|
PageMsg::Content(_) => Err(Error::TypeError),
|
||||||
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
|
PageMsg::Controls(msg) => msg.try_into(),
|
||||||
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,20 +172,33 @@ where
|
|||||||
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 {
|
||||||
PageMsg::Content(_) => Err(Error::TypeError),
|
PageMsg::Content(_) => Err(Error::TypeError),
|
||||||
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
|
PageMsg::Controls(msg) => msg.try_into(),
|
||||||
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F> ComponentMsgObj for painter::Painter<F>
|
||||||
|
where
|
||||||
|
F: FnMut(geometry::Rect),
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
let action: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
|
let action: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
|
||||||
let description: Option<StrBuffer> =
|
let description: Option<StrBuffer> =
|
||||||
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
let verb: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_verb)?.try_into_option()?;
|
let verb: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_verb, "CONFIRM".into())?;
|
||||||
let reverse: bool = kwargs.get(Qstr::MP_QSTR_reverse)?.try_into()?;
|
let verb_cancel: Option<StrBuffer> = kwargs
|
||||||
|
.get(Qstr::MP_QSTR_verb_cancel)
|
||||||
|
.unwrap_or_else(|_| Obj::const_none())
|
||||||
|
.try_into_option()?;
|
||||||
|
let reverse: bool = kwargs.get_or(Qstr::MP_QSTR_reverse, false)?;
|
||||||
|
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||||
|
|
||||||
let paragraphs = {
|
let paragraphs = {
|
||||||
let action = action.unwrap_or_default();
|
let action = action.unwrap_or_default();
|
||||||
@ -154,12 +216,99 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
|||||||
paragraphs
|
paragraphs
|
||||||
};
|
};
|
||||||
|
|
||||||
let buttons = Button::left_right(
|
let obj = if hold {
|
||||||
|
LayoutObj::new(
|
||||||
|
Frame::new(title, SwipeHoldPage::new(paragraphs, theme::BG)).into_child(),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
let buttons = Button::cancel_confirm_text(verb_cancel, verb);
|
||||||
|
LayoutObj::new(
|
||||||
|
Frame::new(title, SwipePage::new(paragraphs, buttons, theme::BG)).into_child(),
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_blob(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: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
|
||||||
|
let description: StrBuffer =
|
||||||
|
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
|
||||||
|
let extra: StrBuffer = kwargs.get_or(Qstr::MP_QSTR_extra, StrBuffer::empty())?;
|
||||||
|
let verb_cancel: Option<StrBuffer> = kwargs
|
||||||
|
.get(Qstr::MP_QSTR_verb_cancel)
|
||||||
|
.unwrap_or_else(|_| Obj::const_none())
|
||||||
|
.try_into_option()?;
|
||||||
|
let _ask_pagination: bool = kwargs.get_or(Qstr::MP_QSTR_ask_pagination, false)?;
|
||||||
|
let hold: bool = kwargs.get_or(Qstr::MP_QSTR_hold, false)?;
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, description)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_BOLD, extra)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, data);
|
||||||
|
|
||||||
|
let obj = if hold {
|
||||||
|
LayoutObj::new(
|
||||||
|
Frame::new(title, SwipeHoldPage::new(paragraphs, theme::BG)).into_child(),
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
let buttons = Button::cancel_confirm_text(verb_cancel, "CONFIRM".into());
|
||||||
|
LayoutObj::new(
|
||||||
|
Frame::new(title, SwipePage::new(paragraphs, buttons, theme::BG)).into_child(),
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_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 buttons = Button::cancel_confirm(
|
||||||
|
Button::with_text(verb_cancel),
|
||||||
|
Button::with_text("CONFIRM".into()).styled(theme::button_confirm()),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new(
|
||||||
|
title,
|
||||||
|
Dialog::new(
|
||||||
|
painter::qrcode_painter(address, theme::QR_SIDE_MAX, case_sensitive),
|
||||||
|
buttons,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_border(theme::borders())
|
||||||
|
.into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_output(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 description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||||
|
let value: StrBuffer = kwargs.get(Qstr::MP_QSTR_value)?.try_into()?;
|
||||||
|
let verb = "NEXT";
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, description)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, value);
|
||||||
|
|
||||||
|
let buttons = Button::cancel_confirm(
|
||||||
Button::with_icon(theme::ICON_CANCEL),
|
Button::with_icon(theme::ICON_CANCEL),
|
||||||
|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| false),
|
Button::with_text(verb).styled(theme::button_confirm()),
|
||||||
Button::with_text(verb.unwrap_or_else(|| "CONFIRM".into()))
|
2,
|
||||||
.styled(theme::button_confirm()),
|
|
||||||
|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| true),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let obj = LayoutObj::new(
|
let obj = LayoutObj::new(
|
||||||
@ -170,15 +319,226 @@ extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut M
|
|||||||
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 new_confirm_total(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 description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||||
|
let value: StrBuffer = kwargs.get(Qstr::MP_QSTR_value)?.try_into()?;
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, description)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, value);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new(title, SwipeHoldPage::new(paragraphs, theme::BG)).into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_joint_total(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let spending_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_spending_amount)?.try_into()?;
|
||||||
|
let total_amount: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_amount)?.try_into()?;
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "You are contributing:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, spending_amount)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "To the total amount:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, total_amount);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new(
|
||||||
|
"JOINT TRANSACTION",
|
||||||
|
SwipeHoldPage::new(paragraphs, theme::BG),
|
||||||
|
)
|
||||||
|
.into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_modify_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let address: StrBuffer = kwargs.get(Qstr::MP_QSTR_address)?.try_into()?;
|
||||||
|
let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
|
||||||
|
let amount_change: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_change)?.try_into()?;
|
||||||
|
let amount_new: StrBuffer = kwargs.get(Qstr::MP_QSTR_amount_new)?.try_into()?;
|
||||||
|
|
||||||
|
let description = if sign < 0 {
|
||||||
|
"Decrease amount by:"
|
||||||
|
} else {
|
||||||
|
"Increase amount by:"
|
||||||
|
};
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "Address:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, address)
|
||||||
|
// FIXME pagebreak
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, description.into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, amount_change)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "New amount:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, amount_new);
|
||||||
|
|
||||||
|
let buttons = Button::cancel_confirm(
|
||||||
|
Button::with_icon(theme::ICON_CANCEL),
|
||||||
|
Button::with_text("NEXT").styled(theme::button_confirm()),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new(
|
||||||
|
"MODIFY AMOUNT",
|
||||||
|
SwipePage::new(paragraphs, buttons, theme::BG),
|
||||||
|
)
|
||||||
|
.into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_modify_fee(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let sign: i32 = kwargs.get(Qstr::MP_QSTR_sign)?.try_into()?;
|
||||||
|
let user_fee_change: StrBuffer = kwargs.get(Qstr::MP_QSTR_user_fee_change)?.try_into()?;
|
||||||
|
let total_fee_new: StrBuffer = kwargs.get(Qstr::MP_QSTR_total_fee_new)?.try_into()?;
|
||||||
|
|
||||||
|
let (description, change) = match sign {
|
||||||
|
s if s < 0 => ("Decrease your fee by:", user_fee_change),
|
||||||
|
s if s > 0 => ("Increase your fee by:", user_fee_change),
|
||||||
|
_ => ("Your fee did not change.", StrBuffer::empty()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, description.into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, change)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "\nTransaction fee:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_MONO, total_fee_new);
|
||||||
|
|
||||||
|
let buttons = Button::cancel_confirm(
|
||||||
|
Button::with_icon(theme::ICON_CANCEL),
|
||||||
|
Button::with_text("NEXT").styled(theme::button_confirm()),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new("MODIFY FEE", SwipePage::new(paragraphs, buttons, theme::BG)).into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_show_warning(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 description: StrBuffer =
|
||||||
|
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
|
||||||
|
|
||||||
|
let buttons = Button::cancel_confirm(
|
||||||
|
Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()),
|
||||||
|
Button::with_text("CONTINUE").styled(theme::button_reset()),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
IconDialog::new(theme::IMAGE_WARN, title, buttons).with_description(description),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_show_success(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 description: StrBuffer =
|
||||||
|
kwargs.get_or(Qstr::MP_QSTR_description, StrBuffer::empty())?;
|
||||||
|
let button: StrBuffer = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?;
|
||||||
|
|
||||||
|
let buttons = component::Map::new(
|
||||||
|
Button::with_text(button).styled(theme::button_confirm()),
|
||||||
|
|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed),
|
||||||
|
);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
IconDialog::new(theme::IMAGE_SUCCESS, title, buttons).with_description(description),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_payment_request(
|
||||||
|
n_args: usize,
|
||||||
|
args: *const Obj,
|
||||||
|
kwargs: *mut Map,
|
||||||
|
) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let description: StrBuffer = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?;
|
||||||
|
let memos: Obj = kwargs.get(Qstr::MP_QSTR_memos)?;
|
||||||
|
|
||||||
|
let mut paragraphs =
|
||||||
|
Paragraphs::new().add::<theme::TTDefaultText>(theme::FONT_NORMAL, description);
|
||||||
|
|
||||||
|
let mut iter_buf = IterBuf::new();
|
||||||
|
let iter = Iter::try_from_obj_with_buf(memos, &mut iter_buf)?;
|
||||||
|
for memo in iter {
|
||||||
|
let text: StrBuffer = memo.try_into()?;
|
||||||
|
paragraphs = paragraphs.add::<theme::TTDefaultText>(theme::FONT_NORMAL, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = Button::cancel_info_confirm("CONFIRM", "DETAILS");
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new(
|
||||||
|
"SENDING",
|
||||||
|
SwipePage::new(paragraphs, buttons, theme::BG).with_button_rows(2),
|
||||||
|
)
|
||||||
|
.into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let coin_name: StrBuffer = kwargs.get(Qstr::MP_QSTR_coin_name)?.try_into()?;
|
||||||
|
let max_rounds: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_rounds)?.try_into()?;
|
||||||
|
let max_feerate: StrBuffer = kwargs.get(Qstr::MP_QSTR_max_feerate)?.try_into()?;
|
||||||
|
|
||||||
|
let paragraphs = Paragraphs::new()
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "Coin name:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_BOLD, coin_name)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "Maximum rounds:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_BOLD, max_rounds)
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_NORMAL, "Maximum mining fee:".into())
|
||||||
|
.add::<theme::TTDefaultText>(theme::FONT_BOLD, max_feerate);
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
Frame::new(
|
||||||
|
"AUTHORIZE COINJOIN",
|
||||||
|
SwipeHoldPage::new(paragraphs, theme::BG),
|
||||||
|
)
|
||||||
|
.into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
extern "C" fn new_request_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
let block = move |_args: &[Obj], kwargs: &Map| {
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
let prompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||||
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
|
let subprompt: StrBuffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
|
||||||
let allow_cancel: Option<bool> =
|
let allow_cancel: bool = kwargs.get_or(Qstr::MP_QSTR_allow_cancel, true)?;
|
||||||
kwargs.get(Qstr::MP_QSTR_allow_cancel)?.try_into_option()?;
|
|
||||||
let warning: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_warning)?.try_into_option()?;
|
let warning: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_warning)?.try_into_option()?;
|
||||||
let obj = LayoutObj::new(
|
let obj = LayoutObj::new(
|
||||||
PinKeyboard::new(prompt, subprompt, warning, allow_cancel.unwrap_or(true)).into_child(),
|
PinKeyboard::new(prompt, subprompt, warning, allow_cancel).into_child(),
|
||||||
)?;
|
)?;
|
||||||
Ok(obj.into())
|
Ok(obj.into())
|
||||||
};
|
};
|
||||||
@ -233,16 +593,119 @@ pub static mp_module_trezorui2: Module = obj_module! {
|
|||||||
/// description: str | None = None,
|
/// description: str | None = None,
|
||||||
/// verb: str | None = None,
|
/// verb: str | None = None,
|
||||||
/// verb_cancel: str | None = None,
|
/// verb_cancel: str | None = None,
|
||||||
/// hold: bool | None = None,
|
/// hold: bool = False,
|
||||||
/// reverse: bool = False,
|
/// reverse: bool = False,
|
||||||
/// ) -> object:
|
/// ) -> object:
|
||||||
/// """Confirm action."""
|
/// """Confirm action."""
|
||||||
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
|
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_blob(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// data: str,
|
||||||
|
/// description: str = "",
|
||||||
|
/// extra: str = "",
|
||||||
|
/// verb_cancel: str | None = None,
|
||||||
|
/// ask_pagination: bool = False,
|
||||||
|
/// hold: bool = False,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm byte sequence data."""
|
||||||
|
Qstr::MP_QSTR_confirm_blob => obj_fn_kw!(0, new_confirm_blob).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, new_show_qr).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_output(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// description: str,
|
||||||
|
/// value: str,
|
||||||
|
/// verb: str = "NEXT",
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm output."""
|
||||||
|
Qstr::MP_QSTR_confirm_output => obj_fn_kw!(0, new_confirm_output).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_total(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// description: str,
|
||||||
|
/// value: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm total."""
|
||||||
|
Qstr::MP_QSTR_confirm_total => obj_fn_kw!(0, new_confirm_total).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_joint_total(
|
||||||
|
/// *,
|
||||||
|
/// spending_amount: str,
|
||||||
|
/// total_amount: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm total if there are external inputs."""
|
||||||
|
Qstr::MP_QSTR_confirm_joint_total => obj_fn_kw!(0, new_confirm_joint_total).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_modify_output(
|
||||||
|
/// *,
|
||||||
|
/// address: str,
|
||||||
|
/// sign: int,
|
||||||
|
/// amount_change: str,
|
||||||
|
/// amount_new: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Decrease or increase amount for given address."""
|
||||||
|
Qstr::MP_QSTR_confirm_modify_output => obj_fn_kw!(0, new_confirm_modify_output).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_modify_fee(
|
||||||
|
/// *,
|
||||||
|
/// sign: int,
|
||||||
|
/// user_fee_change: str,
|
||||||
|
/// total_fee_new: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Decrease or increase transaction fee."""
|
||||||
|
Qstr::MP_QSTR_confirm_modify_fee => obj_fn_kw!(0, new_confirm_modify_fee).as_obj(),
|
||||||
|
|
||||||
|
/// def show_warning(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// description: str = "",
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Warning modal."""
|
||||||
|
Qstr::MP_QSTR_show_warning => obj_fn_kw!(0, new_show_warning).as_obj(),
|
||||||
|
|
||||||
|
/// def show_success(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// button: str,
|
||||||
|
/// description: str = "",
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Success modal."""
|
||||||
|
Qstr::MP_QSTR_show_success => obj_fn_kw!(0, new_show_success).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_payment_request(
|
||||||
|
/// *,
|
||||||
|
/// description: str,
|
||||||
|
/// memos: Iterable[str],
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm payment request."""
|
||||||
|
Qstr::MP_QSTR_confirm_payment_request => obj_fn_kw!(0, new_confirm_payment_request).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_coinjoin(
|
||||||
|
/// *,
|
||||||
|
/// coin_name: str,
|
||||||
|
/// max_rounds: str,
|
||||||
|
/// max_feerate: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm coinjoin authorization."""
|
||||||
|
Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(),
|
||||||
|
|
||||||
/// def request_pin(
|
/// def request_pin(
|
||||||
/// *,
|
/// *,
|
||||||
/// prompt: str,
|
/// prompt: str,
|
||||||
/// subprompt: str | None = None,
|
/// subprompt: str,
|
||||||
/// allow_cancel: bool = True,
|
/// allow_cancel: bool = True,
|
||||||
/// warning: str | None = None,
|
/// warning: str | None = None,
|
||||||
/// ) -> str | object:
|
/// ) -> str | object:
|
||||||
@ -295,12 +758,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn trace_example_layout() {
|
fn trace_example_layout() {
|
||||||
let buttons = Button::left_right(
|
let buttons =
|
||||||
Button::with_text("Left"),
|
Button::cancel_confirm(Button::with_text("Left"), Button::with_text("Right"), 1);
|
||||||
|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| false),
|
|
||||||
Button::with_text("Right"),
|
|
||||||
|msg| (matches!(msg, ButtonMsg::Clicked)).then(|| true),
|
|
||||||
);
|
|
||||||
let mut layout = Dialog::new(
|
let mut layout = Dialog::new(
|
||||||
FormattedText::new::<theme::TTDefaultText>(
|
FormattedText::new::<theme::TTDefaultText>(
|
||||||
"Testing text layout, with some text, and some more text. And {param}",
|
"Testing text layout, with some text, and some more text. And {param}",
|
||||||
|
BIN
core/embed/rust/src/ui/model_tt/res/error.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/error.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tt/res/success.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/success.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tt/res/warn.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/warn.toif
Normal file
Binary file not shown.
@ -39,10 +39,13 @@ pub const GREY_DARK: Color = Color::rgb(51, 51, 51); // greyer
|
|||||||
// Commonly used corner radius (i.e. for buttons).
|
// Commonly used corner radius (i.e. for buttons).
|
||||||
pub const RADIUS: u8 = 2;
|
pub const RADIUS: u8 = 2;
|
||||||
|
|
||||||
|
// Full-size QR code.
|
||||||
|
pub const QR_SIDE_MAX: u32 = 140;
|
||||||
|
|
||||||
// Size of icons in the UI (i.e. inside buttons).
|
// Size of icons in the UI (i.e. inside buttons).
|
||||||
pub const ICON_SIZE: i32 = 16;
|
pub const ICON_SIZE: i32 = 16;
|
||||||
|
|
||||||
// UI icons.
|
// UI icons (greyscale).
|
||||||
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif");
|
pub const ICON_CANCEL: &[u8] = include_res!("model_tt/res/cancel.toif");
|
||||||
pub const ICON_CONFIRM: &[u8] = include_res!("model_tt/res/confirm.toif");
|
pub const ICON_CONFIRM: &[u8] = include_res!("model_tt/res/confirm.toif");
|
||||||
pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
|
pub const ICON_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
|
||||||
@ -50,6 +53,11 @@ pub const ICON_BACK: &[u8] = include_res!("model_tt/res/back.toif");
|
|||||||
pub const ICON_CLICK: &[u8] = include_res!("model_tt/res/click.toif");
|
pub const ICON_CLICK: &[u8] = include_res!("model_tt/res/click.toif");
|
||||||
pub const ICON_NEXT: &[u8] = include_res!("model_tt/res/next.toif");
|
pub const ICON_NEXT: &[u8] = include_res!("model_tt/res/next.toif");
|
||||||
|
|
||||||
|
// Large, color icons.
|
||||||
|
pub const IMAGE_WARN: &[u8] = include_res!("model_tt/res/warn.toif");
|
||||||
|
pub const IMAGE_SUCCESS: &[u8] = include_res!("model_tt/res/success.toif");
|
||||||
|
pub const IMAGE_ERROR: &[u8] = include_res!("model_tt/res/error.toif");
|
||||||
|
|
||||||
// Scrollbar/PIN dots.
|
// Scrollbar/PIN dots.
|
||||||
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif");
|
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif");
|
||||||
pub const DOT_INACTIVE: &[u8] = include_res!("model_tt/res/scroll-inactive.toif");
|
pub const DOT_INACTIVE: &[u8] = include_res!("model_tt/res/scroll-inactive.toif");
|
||||||
@ -95,6 +103,22 @@ pub fn label_page_hint() -> LabelStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn label_warning() -> LabelStyle {
|
||||||
|
LabelStyle {
|
||||||
|
font: FONT_MEDIUM,
|
||||||
|
text_color: FG,
|
||||||
|
background_color: BG,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label_warning_value() -> LabelStyle {
|
||||||
|
LabelStyle {
|
||||||
|
font: FONT_NORMAL,
|
||||||
|
text_color: OFF_WHITE,
|
||||||
|
background_color: BG,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn button_default() -> ButtonStyleSheet {
|
pub fn button_default() -> ButtonStyleSheet {
|
||||||
ButtonStyleSheet {
|
ButtonStyleSheet {
|
||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
|
@ -64,17 +64,131 @@ def confirm_action(
|
|||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
verb: str | None = None,
|
verb: str | None = None,
|
||||||
verb_cancel: str | None = None,
|
verb_cancel: str | None = None,
|
||||||
hold: bool | None = None,
|
hold: bool = False,
|
||||||
reverse: bool = False,
|
reverse: bool = False,
|
||||||
) -> object:
|
) -> object:
|
||||||
"""Confirm action."""
|
"""Confirm action."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_blob(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
data: str,
|
||||||
|
description: str = "",
|
||||||
|
extra: str = "",
|
||||||
|
verb_cancel: str | None = None,
|
||||||
|
ask_pagination: bool = False,
|
||||||
|
hold: bool = False,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm byte sequence data."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def show_qr(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
address: str,
|
||||||
|
verb_cancel: str,
|
||||||
|
case_sensitive: bool,
|
||||||
|
) -> object:
|
||||||
|
"""Show QR code."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_output(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
description: str,
|
||||||
|
value: str,
|
||||||
|
verb: str = "NEXT",
|
||||||
|
) -> object:
|
||||||
|
"""Confirm output."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_total(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
description: str,
|
||||||
|
value: str,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm total."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_joint_total(
|
||||||
|
*,
|
||||||
|
spending_amount: str,
|
||||||
|
total_amount: str,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm total if there are external inputs."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_modify_output(
|
||||||
|
*,
|
||||||
|
address: str,
|
||||||
|
sign: int,
|
||||||
|
amount_change: str,
|
||||||
|
amount_new: str,
|
||||||
|
) -> object:
|
||||||
|
"""Decrease or increase amount for given address."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_modify_fee(
|
||||||
|
*,
|
||||||
|
sign: int,
|
||||||
|
user_fee_change: str,
|
||||||
|
total_fee_new: str,
|
||||||
|
) -> object:
|
||||||
|
"""Decrease or increase transaction fee."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def show_warning(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
description: str = "",
|
||||||
|
) -> object:
|
||||||
|
"""Warning modal."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def show_success(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
button: str,
|
||||||
|
description: str = "",
|
||||||
|
) -> object:
|
||||||
|
"""Success modal."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_payment_request(
|
||||||
|
*,
|
||||||
|
description: str,
|
||||||
|
memos: Iterable[str],
|
||||||
|
) -> object:
|
||||||
|
"""Confirm payment request."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tt/layout.rs
|
||||||
|
def confirm_coinjoin(
|
||||||
|
*,
|
||||||
|
coin_name: str,
|
||||||
|
max_rounds: str,
|
||||||
|
max_feerate: str,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm coinjoin authorization."""
|
||||||
|
|
||||||
|
|
||||||
# rust/src/ui/model_tt/layout.rs
|
# rust/src/ui/model_tt/layout.rs
|
||||||
def request_pin(
|
def request_pin(
|
||||||
*,
|
*,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
subprompt: str | None = None,
|
subprompt: str,
|
||||||
allow_cancel: bool = True,
|
allow_cancel: bool = True,
|
||||||
warning: str | None = None,
|
warning: str | None = None,
|
||||||
) -> str | object:
|
) -> str | object:
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
from ubinascii import hexlify
|
||||||
|
|
||||||
from trezor import io, log, loop, ui, wire, workflow
|
from trezor import io, log, loop, ui, wire, workflow
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
@ -120,20 +121,16 @@ async def confirm_action(
|
|||||||
) -> None:
|
) -> None:
|
||||||
if isinstance(verb, bytes) or isinstance(verb_cancel, bytes):
|
if isinstance(verb, bytes) or isinstance(verb_cancel, bytes):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
elif isinstance(verb, str):
|
if isinstance(verb, str):
|
||||||
verb = verb.upper()
|
verb = verb.upper()
|
||||||
|
if isinstance(verb_cancel, str):
|
||||||
|
verb_cancel = verb_cancel.upper()
|
||||||
|
|
||||||
if description is not None and description_param is not None:
|
if description is not None and description_param is not None:
|
||||||
if description_param_font != ui.BOLD:
|
if description_param_font != ui.BOLD:
|
||||||
log.error(__name__, "confirm_action description_param_font not implemented")
|
log.error(__name__, "confirm_action description_param_font not implemented")
|
||||||
description = description.format(description_param)
|
description = description.format(description_param)
|
||||||
|
|
||||||
if hold:
|
|
||||||
log.error(__name__, "confirm_action hold not implemented")
|
|
||||||
|
|
||||||
if verb_cancel:
|
|
||||||
log.error(__name__, "confirm_action verb_cancel not implemented")
|
|
||||||
|
|
||||||
result = await interact(
|
result = await interact(
|
||||||
ctx,
|
ctx,
|
||||||
_RustLayout(
|
_RustLayout(
|
||||||
@ -142,6 +139,7 @@ async def confirm_action(
|
|||||||
action=action,
|
action=action,
|
||||||
description=description,
|
description=description,
|
||||||
verb=verb,
|
verb=verb,
|
||||||
|
verb_cancel=verb_cancel,
|
||||||
hold=hold,
|
hold=hold,
|
||||||
reverse=reverse,
|
reverse=reverse,
|
||||||
)
|
)
|
||||||
@ -172,21 +170,51 @@ async def confirm_backup(ctx: wire.GenericContext) -> bool:
|
|||||||
async def confirm_path_warning(
|
async def confirm_path_warning(
|
||||||
ctx: wire.GenericContext, path: str, path_type: str = "Path"
|
ctx: wire.GenericContext, path: str, path_type: str = "Path"
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.show_warning(
|
||||||
|
title="Unknown path",
|
||||||
|
description=path,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"path_warning",
|
||||||
|
ButtonRequestType.UnknownDerivationPath,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
|
def _show_xpub(xpub: str, title: str, cancel: str) -> ui.Layout:
|
||||||
|
content = _RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=title,
|
||||||
|
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:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_show_xpub(xpub, title, cancel),
|
||||||
|
"show_xpub",
|
||||||
|
ButtonRequestType.PublicKey,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def show_address(
|
async def show_address(
|
||||||
ctx: wire.GenericContext,
|
ctx: wire.GenericContext,
|
||||||
address: str,
|
address: str,
|
||||||
*,
|
*,
|
||||||
case_sensitive: bool = True,
|
|
||||||
address_qr: str | None = None,
|
address_qr: str | None = None,
|
||||||
|
case_sensitive: bool = True,
|
||||||
title: str = "Confirm address",
|
title: str = "Confirm address",
|
||||||
network: str | None = None,
|
network: str | None = None,
|
||||||
multisig_index: int | None = None,
|
multisig_index: int | None = None,
|
||||||
@ -194,7 +222,54 @@ 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:
|
||||||
raise NotImplementedError
|
is_multisig = len(xpubs) > 0
|
||||||
|
while True:
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=title.upper(),
|
||||||
|
data=address,
|
||||||
|
description=network or "",
|
||||||
|
extra=address_extra or "",
|
||||||
|
verb_cancel="QR",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"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(
|
||||||
@ -277,25 +352,27 @@ def show_warning(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def show_success(
|
async def show_success(
|
||||||
ctx: wire.GenericContext,
|
ctx: wire.GenericContext,
|
||||||
br_type: str,
|
br_type: str,
|
||||||
content: str,
|
content: str,
|
||||||
subheader: str | None = None,
|
subheader: str | None = None,
|
||||||
button: str = "Continue",
|
button: str = "Continue",
|
||||||
) -> Awaitable[None]:
|
) -> None:
|
||||||
return _show_modal(
|
result = await interact(
|
||||||
ctx,
|
ctx,
|
||||||
br_type=br_type,
|
_RustLayout(
|
||||||
br_code=ButtonRequestType.Success,
|
trezorui2.show_success(
|
||||||
header="Success",
|
title=content,
|
||||||
subheader=subheader,
|
description=subheader or "",
|
||||||
content=content,
|
button=button.upper(),
|
||||||
button_confirm=button,
|
)
|
||||||
button_cancel=None,
|
),
|
||||||
icon=ui.ICON_CONFIRM,
|
br_type,
|
||||||
icon_color=ui.GREEN,
|
ButtonRequestType.Success,
|
||||||
)
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_output(
|
async def confirm_output(
|
||||||
@ -303,7 +380,7 @@ async def confirm_output(
|
|||||||
address: str,
|
address: str,
|
||||||
amount: str,
|
amount: str,
|
||||||
font_amount: int = ui.NORMAL, # TODO cleanup @ redesign
|
font_amount: int = ui.NORMAL, # TODO cleanup @ redesign
|
||||||
title: str = "Confirm sending",
|
title: str = "SENDING",
|
||||||
subtitle: str | None = None, # TODO cleanup @ redesign
|
subtitle: str | None = None, # TODO cleanup @ redesign
|
||||||
color_to: int = ui.FG, # TODO cleanup @ redesign
|
color_to: int = ui.FG, # TODO cleanup @ redesign
|
||||||
to_str: str = " to\n", # TODO cleanup @ redesign
|
to_str: str = " to\n", # TODO cleanup @ redesign
|
||||||
@ -313,7 +390,39 @@ async def confirm_output(
|
|||||||
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
br_code: ButtonRequestType = ButtonRequestType.ConfirmOutput,
|
||||||
icon: str = ui.ICON_SEND,
|
icon: str = ui.ICON_SEND,
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
title = title.upper()
|
||||||
|
if title.startswith("CONFIRM "):
|
||||||
|
title = title[len("CONFIRM ") :]
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_output(
|
||||||
|
title=title,
|
||||||
|
description="To:",
|
||||||
|
value=address,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_output",
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_output(
|
||||||
|
title=title,
|
||||||
|
description="Amount:",
|
||||||
|
value=amount,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_output",
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_payment_request(
|
async def confirm_payment_request(
|
||||||
@ -322,7 +431,25 @@ async def confirm_payment_request(
|
|||||||
amount: str,
|
amount: str,
|
||||||
memos: list[str],
|
memos: list[str],
|
||||||
) -> Any:
|
) -> Any:
|
||||||
raise NotImplementedError
|
from ...components.common import confirm
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_payment_request(
|
||||||
|
description=f"{amount} to\n{recipient_name}",
|
||||||
|
memos=memos,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_payment_request",
|
||||||
|
ButtonRequestType.ConfirmOutput,
|
||||||
|
)
|
||||||
|
if result is trezorui2.CONFIRMED:
|
||||||
|
return confirm.CONFIRMED
|
||||||
|
elif result is trezorui2.INFO:
|
||||||
|
return confirm.INFO
|
||||||
|
else:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def should_show_more(
|
async def should_show_more(
|
||||||
@ -350,7 +477,25 @@ async def confirm_blob(
|
|||||||
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||||||
ask_pagination: bool = False,
|
ask_pagination: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
if isinstance(data, bytes):
|
||||||
|
data = hexlify(data).decode()
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=title.upper(),
|
||||||
|
description=description or "",
|
||||||
|
data=data,
|
||||||
|
ask_pagination=ask_pagination,
|
||||||
|
hold=hold,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
def confirm_address(
|
def confirm_address(
|
||||||
@ -410,20 +555,60 @@ async def confirm_total(
|
|||||||
total_amount: str,
|
total_amount: str,
|
||||||
fee_amount: str,
|
fee_amount: str,
|
||||||
fee_rate_amount: str | None = None,
|
fee_rate_amount: str | None = None,
|
||||||
title: str = "Confirm transaction",
|
title: str = "SENDING",
|
||||||
total_label: str = "Total amount:\n",
|
total_label: str = "Total amount:\n",
|
||||||
fee_label: str = "\nincluding fee:\n",
|
fee_label: str = "\nincluding fee:\n",
|
||||||
icon_color: int = ui.GREEN,
|
icon_color: int = ui.GREEN,
|
||||||
br_type: str = "confirm_total",
|
br_type: str = "confirm_total",
|
||||||
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
br_code: ButtonRequestType = ButtonRequestType.SignTx,
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_output(
|
||||||
|
title=title.upper(),
|
||||||
|
description="Fee:",
|
||||||
|
value=fee_amount,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_total",
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_total(
|
||||||
|
title=title.upper(),
|
||||||
|
description="Total amount:",
|
||||||
|
value=total_amount,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_total",
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_joint_total(
|
async def confirm_joint_total(
|
||||||
ctx: wire.GenericContext, spending_amount: str, total_amount: str
|
ctx: wire.GenericContext, spending_amount: str, total_amount: str
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_joint_total(
|
||||||
|
spending_amount=spending_amount,
|
||||||
|
total_amount=total_amount,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_joint_total",
|
||||||
|
ButtonRequestType.SignTx,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_metadata(
|
async def confirm_metadata(
|
||||||
@ -440,13 +625,59 @@ async def confirm_metadata(
|
|||||||
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
icon_color: int = ui.GREEN, # TODO cleanup @ redesign
|
||||||
larger_vspace: bool = False, # TODO cleanup @ redesign
|
larger_vspace: bool = False, # TODO cleanup @ redesign
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
if param:
|
||||||
|
content = content.format(param)
|
||||||
|
|
||||||
|
if br_type == "fee_over_threshold":
|
||||||
|
layout = trezorui2.show_warning(
|
||||||
|
title="Unusually high fee",
|
||||||
|
description=param or "",
|
||||||
|
)
|
||||||
|
elif br_type == "change_count_over_threshold":
|
||||||
|
layout = trezorui2.show_warning(
|
||||||
|
title="A lot of change-outputs",
|
||||||
|
description=f"{param} outputs" if param is not None else "",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if param is not None:
|
||||||
|
content = content.format(param)
|
||||||
|
# TODO: "unverified external inputs"
|
||||||
|
|
||||||
|
layout = trezorui2.confirm_action(
|
||||||
|
title=title.upper(),
|
||||||
|
verb="NEXT",
|
||||||
|
description=content,
|
||||||
|
hold=hold,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(layout),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_replacement(
|
async def confirm_replacement(
|
||||||
ctx: wire.GenericContext, description: str, txid: str
|
ctx: wire.GenericContext, description: str, txid: str
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=description.upper(),
|
||||||
|
description="Confirm transaction ID:",
|
||||||
|
data=txid,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"confirm_replacement",
|
||||||
|
ButtonRequestType.SignTx,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_modify_output(
|
async def confirm_modify_output(
|
||||||
@ -456,7 +687,21 @@ async def confirm_modify_output(
|
|||||||
amount_change: str,
|
amount_change: str,
|
||||||
amount_new: str,
|
amount_new: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_modify_output(
|
||||||
|
address=address,
|
||||||
|
sign=sign,
|
||||||
|
amount_change=amount_change,
|
||||||
|
amount_new=amount_new,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"modify_output",
|
||||||
|
ButtonRequestType.ConfirmOutput,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_modify_fee(
|
async def confirm_modify_fee(
|
||||||
@ -465,13 +710,39 @@ async def confirm_modify_fee(
|
|||||||
user_fee_change: str,
|
user_fee_change: str,
|
||||||
total_fee_new: str,
|
total_fee_new: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_modify_fee(
|
||||||
|
sign=sign,
|
||||||
|
user_fee_change=user_fee_change,
|
||||||
|
total_fee_new=total_fee_new,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"modify_fee",
|
||||||
|
ButtonRequestType.SignTx,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def confirm_coinjoin(
|
async def confirm_coinjoin(
|
||||||
ctx: wire.GenericContext, coin_name: str, max_rounds: int, max_fee_per_vbyte: str
|
ctx: wire.GenericContext, coin_name: str, max_rounds: int, max_fee_per_vbyte: str
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_coinjoin(
|
||||||
|
coin_name=coin_name,
|
||||||
|
max_rounds=str(max_rounds),
|
||||||
|
max_feerate=f"{max_fee_per_vbyte} sats/vbyte",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"coinjoin_final",
|
||||||
|
ButtonRequestType.Other,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
# TODO cleanup @ redesign
|
# TODO cleanup @ redesign
|
||||||
@ -484,7 +755,42 @@ async def confirm_sign_identity(
|
|||||||
async def confirm_signverify(
|
async def confirm_signverify(
|
||||||
ctx: wire.GenericContext, coin: str, message: str, address: str, verify: bool
|
ctx: wire.GenericContext, coin: str, message: str, address: str, verify: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
if verify:
|
||||||
|
title = f"VERIFY {coin} MESSAGE"
|
||||||
|
br_type = "verify_message"
|
||||||
|
else:
|
||||||
|
title = f"SIGN {coin} MESSAGE"
|
||||||
|
br_type = "sign_message"
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=title,
|
||||||
|
description="Confirm address:",
|
||||||
|
data=address,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
ButtonRequestType.Other,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
_RustLayout(
|
||||||
|
trezorui2.confirm_blob(
|
||||||
|
title=title,
|
||||||
|
description="Confirm message:",
|
||||||
|
data=message,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
ButtonRequestType.Other,
|
||||||
|
)
|
||||||
|
if result is not trezorui2.CONFIRMED:
|
||||||
|
raise wire.ActionCancelled
|
||||||
|
|
||||||
|
|
||||||
async def show_popup(
|
async def show_popup(
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user