mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-26 01:18:28 +00:00
fix(core/rust/ui): recovery/passphrase keyboard fixes
[no changelog]
This commit is contained in:
parent
efe25a6ab4
commit
4eefaffac9
@ -53,6 +53,31 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui2_layout_new_example_obj,
|
|||||||
/// """PIN keyboard."""
|
/// """PIN keyboard."""
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_pin_obj, 0,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_pin_obj, 0,
|
||||||
ui_layout_new_pin);
|
ui_layout_new_pin);
|
||||||
|
|
||||||
|
/// def layout_new_passphrase(
|
||||||
|
/// *,
|
||||||
|
/// prompt: str,
|
||||||
|
/// max_len: int,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Passphrase keyboard."""
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_passphrase_obj, 0,
|
||||||
|
ui_layout_new_passphrase);
|
||||||
|
|
||||||
|
/// def layout_new_bip39(
|
||||||
|
/// *,
|
||||||
|
/// prompt: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """BIP39 keyboard."""
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_bip39_obj, 0,
|
||||||
|
ui_layout_new_bip39);
|
||||||
|
|
||||||
|
/// def layout_new_slip39(
|
||||||
|
/// *,
|
||||||
|
/// prompt: str,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """BIP39 keyboard."""
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_slip39_obj, 0,
|
||||||
|
ui_layout_new_slip39);
|
||||||
#elif TREZOR_MODEL == 1
|
#elif TREZOR_MODEL == 1
|
||||||
/// def layout_new_confirm_text(
|
/// def layout_new_confirm_text(
|
||||||
/// *,
|
/// *,
|
||||||
@ -75,6 +100,12 @@ STATIC const mp_rom_map_elem_t mp_module_trezorui2_globals_table[] = {
|
|||||||
MP_ROM_PTR(&mod_trezorui2_layout_new_example_obj)},
|
MP_ROM_PTR(&mod_trezorui2_layout_new_example_obj)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_layout_new_pin),
|
{MP_ROM_QSTR(MP_QSTR_layout_new_pin),
|
||||||
MP_ROM_PTR(&mod_trezorui2_layout_new_pin_obj)},
|
MP_ROM_PTR(&mod_trezorui2_layout_new_pin_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_layout_new_passphrase),
|
||||||
|
MP_ROM_PTR(&mod_trezorui2_layout_new_passphrase_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_layout_new_bip39),
|
||||||
|
MP_ROM_PTR(&mod_trezorui2_layout_new_bip39_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_layout_new_slip39),
|
||||||
|
MP_ROM_PTR(&mod_trezorui2_layout_new_slip39_obj)},
|
||||||
#elif TREZOR_MODEL == 1
|
#elif TREZOR_MODEL == 1
|
||||||
{MP_ROM_QSTR(MP_QSTR_layout_new_confirm_text),
|
{MP_ROM_QSTR(MP_QSTR_layout_new_confirm_text),
|
||||||
MP_ROM_PTR(&mod_trezorui2_layout_new_confirm_text_obj)},
|
MP_ROM_PTR(&mod_trezorui2_layout_new_confirm_text_obj)},
|
||||||
|
@ -19,6 +19,12 @@ mp_obj_t ui_layout_new_confirm_text(size_t n_args, const mp_obj_t *args,
|
|||||||
mp_map_t *kwargs);
|
mp_map_t *kwargs);
|
||||||
mp_obj_t ui_layout_new_pin(size_t n_args, const mp_obj_t *args,
|
mp_obj_t ui_layout_new_pin(size_t n_args, const mp_obj_t *args,
|
||||||
mp_map_t *kwargs);
|
mp_map_t *kwargs);
|
||||||
|
mp_obj_t ui_layout_new_passphrase(size_t n_args, const mp_obj_t *args,
|
||||||
|
mp_map_t *kwargs);
|
||||||
|
mp_obj_t ui_layout_new_bip39(size_t n_args, const mp_obj_t *args,
|
||||||
|
mp_map_t *kwargs);
|
||||||
|
mp_obj_t ui_layout_new_slip39(size_t n_args, const mp_obj_t *args,
|
||||||
|
mp_map_t *kwargs);
|
||||||
|
|
||||||
#ifdef TREZOR_EMULATOR
|
#ifdef TREZOR_EMULATOR
|
||||||
mp_obj_t ui_debug_layout_type();
|
mp_obj_t ui_debug_layout_type();
|
||||||
|
@ -29,4 +29,5 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_subprompt;
|
MP_QSTR_subprompt;
|
||||||
MP_QSTR_warning;
|
MP_QSTR_warning;
|
||||||
MP_QSTR_allow_cancel;
|
MP_QSTR_allow_cancel;
|
||||||
|
MP_QSTR_max_len;
|
||||||
}
|
}
|
||||||
|
@ -92,4 +92,9 @@ where
|
|||||||
self.inner.paint();
|
self.inner.paint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
sink(self.pad.area);
|
||||||
|
self.inner.bounds(sink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -461,9 +461,9 @@ pub struct GridCellSpan {
|
|||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct LinearPlacement {
|
pub struct LinearPlacement {
|
||||||
axis: Axis,
|
pub axis: Axis,
|
||||||
align: Alignment,
|
pub align: Alignment,
|
||||||
spacing: i32,
|
pub spacing: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinearPlacement {
|
impl LinearPlacement {
|
||||||
|
@ -259,7 +259,8 @@ impl LayoutObj {
|
|||||||
display::rect_stroke(r, color)
|
display::rect_stroke(r, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wireframe(display::screen());
|
// use crate::ui::model_tt::theme;
|
||||||
|
// wireframe(theme::borders());
|
||||||
self.inner.borrow().root.obj_bounds(&mut wireframe);
|
self.inner.borrow().root.obj_bounds(&mut wireframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +257,10 @@ where
|
|||||||
self.paint_background(&style);
|
self.paint_background(&style);
|
||||||
self.paint_content(&style);
|
self.paint_content(&style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
sink(self.area);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
|
@ -7,7 +7,7 @@ use crate::{
|
|||||||
model_tt::{
|
model_tt::{
|
||||||
component::{
|
component::{
|
||||||
keyboard::{
|
keyboard::{
|
||||||
common::{MultiTapKeyboard, TextBox},
|
common::{paint_pending_marker, MultiTapKeyboard, TextBox},
|
||||||
mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT},
|
mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT},
|
||||||
},
|
},
|
||||||
Button, ButtonContent, ButtonMsg,
|
Button, ButtonContent, ButtonMsg,
|
||||||
@ -118,16 +118,7 @@ impl Component for Bip39Input {
|
|||||||
|
|
||||||
// Paint the pending marker.
|
// Paint the pending marker.
|
||||||
if self.multi_tap.pending_key().is_some() {
|
if self.multi_tap.pending_key().is_some() {
|
||||||
// Measure the width of the last character of input.
|
paint_pending_marker(text_baseline, text, style.font, style.text_color);
|
||||||
if let Some(last) = text.last().copied() {
|
|
||||||
let last_width = style.font.text_width(&[last]);
|
|
||||||
// Draw the marker 2px under the start of the baseline of the last character.
|
|
||||||
let marker_origin = text_baseline + Offset::new(width - last_width, 2);
|
|
||||||
// Draw the marker 1px longer than the last character, and 3px thick.
|
|
||||||
let marker_rect =
|
|
||||||
Rect::from_top_left_and_size(marker_origin, Offset::new(last_width + 1, 3));
|
|
||||||
display::rect_fill(marker_rect, style.text_color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint the icon.
|
// Paint the icon.
|
||||||
@ -138,6 +129,10 @@ impl Component for Bip39Input {
|
|||||||
display::icon(icon_center, icon, style.text_color, style.button_color);
|
display::icon(icon_center, icon, style.text_color, style.button_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.button.bounds(sink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bip39Input {
|
impl Bip39Input {
|
||||||
|
@ -2,10 +2,18 @@ use heapless::String;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::component::{Event, EventCtx, TimerToken},
|
ui::{
|
||||||
|
component::{Event, EventCtx, TimerToken},
|
||||||
|
display::{self, Color, Font},
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
},
|
||||||
util::ResultExt,
|
util::ResultExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const HEADER_HEIGHT: i32 = 25;
|
||||||
|
pub const HEADER_PADDING_SIDE: i32 = 5;
|
||||||
|
pub const HEADER_PADDING_BOTTOM: i32 = 12;
|
||||||
|
|
||||||
/// Contains state commonly used in implementations multi-tap keyboards.
|
/// Contains state commonly used in implementations multi-tap keyboards.
|
||||||
pub struct MultiTapKeyboard {
|
pub struct MultiTapKeyboard {
|
||||||
/// Configured timeout after which we cancel currently pending key.
|
/// Configured timeout after which we cancel currently pending key.
|
||||||
@ -199,3 +207,17 @@ impl<const L: usize> TextBox<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paint_pending_marker(text_baseline: Point, text: &[u8], font: Font, color: Color) {
|
||||||
|
// Measure the width of the last character of input.
|
||||||
|
if let Some(last) = text.last().copied() {
|
||||||
|
let width = font.text_width(text);
|
||||||
|
let last_width = font.text_width(&[last]);
|
||||||
|
// Draw the marker 2px under the start of the baseline of the last character.
|
||||||
|
let marker_origin = text_baseline + Offset::new(width - last_width, 2);
|
||||||
|
// Draw the marker 1px longer than the last character, and 3px thick.
|
||||||
|
let marker_rect =
|
||||||
|
Rect::from_top_left_and_size(marker_origin, Offset::new(last_width + 1, 3));
|
||||||
|
display::rect_fill(marker_rect, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Child, Component, Event, EventCtx, Label, Maybe},
|
component::{Child, Component, Event, EventCtx, Label, Maybe},
|
||||||
geometry::{Grid, Rect},
|
geometry::{Alignment, Grid, Rect},
|
||||||
model_tt::{
|
model_tt::{
|
||||||
component::{Button, ButtonMsg},
|
component::{Button, ButtonMsg},
|
||||||
theme,
|
theme,
|
||||||
@ -32,7 +32,7 @@ where
|
|||||||
Self {
|
Self {
|
||||||
prompt: Child::new(Maybe::visible(
|
prompt: Child::new(Maybe::visible(
|
||||||
theme::BG,
|
theme::BG,
|
||||||
Label::left_aligned(prompt, theme::label_default()),
|
Label::centered(prompt, theme::label_keyboard()),
|
||||||
)),
|
)),
|
||||||
back: Child::new(Maybe::hidden(
|
back: Child::new(Maybe::hidden(
|
||||||
theme::BG,
|
theme::BG,
|
||||||
@ -84,10 +84,15 @@ where
|
|||||||
type Msg = MnemonicKeyboardMsg;
|
type Msg = MnemonicKeyboardMsg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
let grid = Grid::new(bounds, 3, 4);
|
let grid =
|
||||||
|
Grid::new(bounds.inset(theme::borders()), 4, 3).with_spacing(theme::KEYBOARD_SPACING);
|
||||||
let back_area = grid.row_col(0, 0);
|
let back_area = grid.row_col(0, 0);
|
||||||
let input_area = grid.row_col(0, 1).union(grid.row_col(0, 3));
|
let input_area = grid.row_col(0, 1).union(grid.row_col(0, 3));
|
||||||
let prompt_area = grid.row_col(0, 0).union(grid.row_col(0, 3));
|
|
||||||
|
let prompt_center = grid.row_col(0, 0).union(grid.row_col(0, 3)).center();
|
||||||
|
let prompt_size = self.prompt.inner().inner().size();
|
||||||
|
let prompt_top_left = prompt_size.snap(prompt_center, Alignment::Center, Alignment::Center);
|
||||||
|
let prompt_area = Rect::from_top_left_and_size(prompt_top_left, prompt_size);
|
||||||
|
|
||||||
self.prompt.place(prompt_area);
|
self.prompt.place(prompt_area);
|
||||||
self.back.place(back_area);
|
self.back.place(back_area);
|
||||||
@ -136,6 +141,15 @@ where
|
|||||||
btn.paint();
|
btn.paint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.prompt.bounds(sink);
|
||||||
|
self.input.bounds(sink);
|
||||||
|
self.back.bounds(sink);
|
||||||
|
for btn in &self.keys {
|
||||||
|
btn.bounds(sink)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MnemonicInput: Component<Msg = MnemonicInputMsg> {
|
pub trait MnemonicInput: Component<Msg = MnemonicInputMsg> {
|
||||||
@ -151,3 +165,11 @@ pub enum MnemonicInputMsg {
|
|||||||
Completed,
|
Completed,
|
||||||
TimedOut,
|
TimedOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for MnemonicKeyboard<T> {
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("MnemonicKeyboard");
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{base::ComponentExt, Child, Component, Event, EventCtx, Never},
|
component::{base::ComponentExt, Child, Component, Event, EventCtx, Never},
|
||||||
display,
|
display,
|
||||||
geometry::{Grid, Rect},
|
geometry::{Grid, Insets, Offset, Rect},
|
||||||
model_tt::component::{
|
model_tt::component::{
|
||||||
button::{Button, ButtonContent, ButtonMsg::Clicked},
|
button::{Button, ButtonContent, ButtonMsg::Clicked},
|
||||||
keyboard::common::{MultiTapKeyboard, TextBox},
|
keyboard::common::{paint_pending_marker, MultiTapKeyboard, TextBox, HEADER_PADDING_SIDE},
|
||||||
swipe::{Swipe, SwipeDirection},
|
swipe::{Swipe, SwipeDirection},
|
||||||
theme,
|
theme, ScrollBar,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,7 +21,8 @@ pub struct PassphraseKeyboard {
|
|||||||
back: Child<Button<&'static str>>,
|
back: Child<Button<&'static str>>,
|
||||||
confirm: Child<Button<&'static str>>,
|
confirm: Child<Button<&'static str>>,
|
||||||
keys: [[Child<Button<&'static str>>; KEY_COUNT]; PAGE_COUNT],
|
keys: [[Child<Button<&'static str>>; KEY_COUNT]; PAGE_COUNT],
|
||||||
key_page: usize,
|
scrollbar: ScrollBar,
|
||||||
|
fade: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const STARTING_PAGE: usize = 1;
|
const STARTING_PAGE: usize = 1;
|
||||||
@ -42,11 +43,12 @@ impl PassphraseKeyboard {
|
|||||||
Self {
|
Self {
|
||||||
page_swipe: Swipe::horizontal(),
|
page_swipe: Swipe::horizontal(),
|
||||||
input: Input::new().into_child(),
|
input: Input::new().into_child(),
|
||||||
confirm: Button::with_text("Confirm")
|
confirm: Button::with_icon(theme::ICON_CONFIRM)
|
||||||
.styled(theme::button_confirm())
|
.styled(theme::button_confirm())
|
||||||
.into_child(),
|
.into_child(),
|
||||||
back: Button::with_text("Back")
|
back: Button::with_icon(theme::ICON_BACK)
|
||||||
.styled(theme::button_clear())
|
.styled(theme::button_reset())
|
||||||
|
.initially_enabled(false)
|
||||||
.into_child(),
|
.into_child(),
|
||||||
keys: KEYBOARD.map(|page| {
|
keys: KEYBOARD.map(|page| {
|
||||||
page.map(|text| {
|
page.map(|text| {
|
||||||
@ -58,7 +60,8 @@ impl PassphraseKeyboard {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
key_page: STARTING_PAGE,
|
scrollbar: ScrollBar::horizontal(),
|
||||||
|
fade: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,18 +75,22 @@ impl PassphraseKeyboard {
|
|||||||
|
|
||||||
fn on_page_swipe(&mut self, ctx: &mut EventCtx, swipe: SwipeDirection) {
|
fn on_page_swipe(&mut self, ctx: &mut EventCtx, swipe: SwipeDirection) {
|
||||||
// Change the page number.
|
// Change the page number.
|
||||||
self.key_page = match swipe {
|
let key_page = self.scrollbar.active_page;
|
||||||
SwipeDirection::Left => (self.key_page as isize + 1) as usize % PAGE_COUNT,
|
let key_page = match swipe {
|
||||||
SwipeDirection::Right => (self.key_page as isize - 1) as usize % PAGE_COUNT,
|
SwipeDirection::Left => (key_page as isize + 1) as usize % PAGE_COUNT,
|
||||||
_ => self.key_page,
|
SwipeDirection::Right => (key_page as isize - 1) as usize % PAGE_COUNT,
|
||||||
|
_ => key_page,
|
||||||
};
|
};
|
||||||
|
self.scrollbar.go_to(key_page);
|
||||||
// Clear the pending state.
|
// Clear the pending state.
|
||||||
self.input
|
self.input
|
||||||
.mutate(ctx, |ctx, i| i.multi_tap.clear_pending_state(ctx));
|
.mutate(ctx, |ctx, i| i.multi_tap.clear_pending_state(ctx));
|
||||||
// Make sure to completely repaint the buttons.
|
// Make sure to completely repaint the buttons.
|
||||||
for btn in &mut self.keys[self.key_page] {
|
for btn in &mut self.keys[key_page] {
|
||||||
btn.request_complete_repaint(ctx);
|
btn.request_complete_repaint(ctx);
|
||||||
}
|
}
|
||||||
|
// Reset backlight to normal level on next paint.
|
||||||
|
self.fade = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn after_edit(&mut self, ctx: &mut EventCtx) {
|
fn after_edit(&mut self, ctx: &mut EventCtx) {
|
||||||
@ -99,16 +106,28 @@ impl Component for PassphraseKeyboard {
|
|||||||
type Msg = PassphraseKeyboardMsg;
|
type Msg = PassphraseKeyboardMsg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
let input_area = Grid::new(bounds, 5, 1).row_col(0, 0);
|
let bounds = bounds.inset(theme::borders());
|
||||||
let confirm_btn_area = Grid::new(bounds, 5, 3).cell(14);
|
|
||||||
let back_btn_area = Grid::new(bounds, 5, 3).cell(12);
|
let input_area = Grid::new(bounds, 5, 1)
|
||||||
let key_grid = Grid::new(bounds, 5, 3);
|
.with_spacing(theme::KEYBOARD_SPACING)
|
||||||
|
.row_col(0, 0);
|
||||||
|
|
||||||
|
let (input_area, scroll_area) = input_area.split_bottom(ScrollBar::DOT_SIZE);
|
||||||
|
let input_area =
|
||||||
|
input_area.inset(Insets::new(0, HEADER_PADDING_SIDE, 2, HEADER_PADDING_SIDE));
|
||||||
|
|
||||||
|
let key_grid = Grid::new(bounds, 5, 3).with_spacing(theme::KEYBOARD_SPACING);
|
||||||
|
let confirm_btn_area = key_grid.cell(14);
|
||||||
|
let back_btn_area = key_grid.cell(12);
|
||||||
|
|
||||||
self.page_swipe.place(bounds);
|
self.page_swipe.place(bounds);
|
||||||
self.input.place(input_area);
|
self.input.place(input_area);
|
||||||
self.confirm.place(confirm_btn_area);
|
self.confirm.place(confirm_btn_area);
|
||||||
self.back.place(back_btn_area);
|
self.back.place(back_btn_area);
|
||||||
for (key, btn) in self.keys[self.key_page].iter_mut().enumerate() {
|
self.scrollbar.place(scroll_area);
|
||||||
|
self.scrollbar
|
||||||
|
.set_count_and_active_page(PAGE_COUNT, STARTING_PAGE);
|
||||||
|
for (key, btn) in self.keys[self.scrollbar.active_page].iter_mut().enumerate() {
|
||||||
// Assign the keys in each page to buttons on a 5x3 grid, starting from the
|
// Assign the keys in each page to buttons on a 5x3 grid, starting from the
|
||||||
// second row.
|
// second row.
|
||||||
let area = key_grid.cell(if key < 9 {
|
let area = key_grid.cell(if key < 9 {
|
||||||
@ -152,7 +171,7 @@ impl Component for PassphraseKeyboard {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
for (key, btn) in self.keys[self.key_page].iter_mut().enumerate() {
|
for (key, btn) in self.keys[self.scrollbar.active_page].iter_mut().enumerate() {
|
||||||
if let Some(Clicked) = btn.event(ctx, event) {
|
if let Some(Clicked) = btn.event(ctx, event) {
|
||||||
// Key button was clicked. If this button is pending, let's cycle the pending
|
// Key button was clicked. If this button is pending, let's cycle the pending
|
||||||
// character in textbox. If not, let's just append the first character.
|
// character in textbox. If not, let's just append the first character.
|
||||||
@ -170,11 +189,27 @@ impl Component for PassphraseKeyboard {
|
|||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
self.input.paint();
|
self.input.paint();
|
||||||
|
self.scrollbar.paint();
|
||||||
self.confirm.paint();
|
self.confirm.paint();
|
||||||
self.back.paint();
|
self.back.paint();
|
||||||
for btn in &mut self.keys[self.key_page] {
|
for btn in &mut self.keys[self.scrollbar.active_page] {
|
||||||
btn.paint();
|
btn.paint();
|
||||||
}
|
}
|
||||||
|
if self.fade {
|
||||||
|
self.fade = false;
|
||||||
|
// Note that this is blocking and takes some time.
|
||||||
|
display::fade_backlight(theme::BACKLIGHT_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.input.bounds(sink);
|
||||||
|
self.scrollbar.bounds(sink);
|
||||||
|
self.confirm.bounds(sink);
|
||||||
|
self.back.bounds(sink);
|
||||||
|
for btn in &self.keys[self.scrollbar.active_page] {
|
||||||
|
btn.bounds(sink)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,14 +242,38 @@ impl Component for Input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
let style = theme::label_default();
|
const TEXT_OFFSET: Offset = Offset::y(8);
|
||||||
|
|
||||||
|
let style = theme::label_default();
|
||||||
|
let text_baseline = self.area.bottom_left() - TEXT_OFFSET;
|
||||||
|
let text = self.textbox.content().as_bytes();
|
||||||
|
|
||||||
|
// Possible optimization is to redraw the background only when pending character
|
||||||
|
// is replaced, or only draw rectangle over the pending character and
|
||||||
|
// marker.
|
||||||
|
display::rect_fill(self.area, theme::BG);
|
||||||
display::text(
|
display::text(
|
||||||
self.area.bottom_left(),
|
text_baseline,
|
||||||
self.textbox.content().as_bytes(),
|
text,
|
||||||
style.font,
|
style.font,
|
||||||
style.text_color,
|
style.text_color,
|
||||||
style.background_color,
|
style.background_color,
|
||||||
);
|
);
|
||||||
|
// Paint the pending marker.
|
||||||
|
if self.multi_tap.pending_key().is_some() {
|
||||||
|
paint_pending_marker(text_baseline, text, style.font, style.text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
sink(self.area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl crate::trace::Trace for PassphraseKeyboard {
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("PassphraseKeyboard");
|
||||||
|
t.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,6 @@ impl<T> PinKeyboard<T>
|
|||||||
where
|
where
|
||||||
T: Deref<Target = [u8]>,
|
T: Deref<Target = [u8]>,
|
||||||
{
|
{
|
||||||
const BUTTON_SPACING: i32 = 8;
|
|
||||||
const HEADER_HEIGHT: i32 = 25;
|
const HEADER_HEIGHT: i32 = 25;
|
||||||
const HEADER_PADDING_SIDE: i32 = 5;
|
const HEADER_PADDING_SIDE: i32 = 5;
|
||||||
const HEADER_PADDING_BOTTOM: i32 = 12;
|
const HEADER_PADDING_BOTTOM: i32 = 12;
|
||||||
@ -57,7 +56,6 @@ where
|
|||||||
major_warning: Option<T>,
|
major_warning: Option<T>,
|
||||||
allow_cancel: bool,
|
allow_cancel: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let area = area.inset(Insets::right(theme::CONTENT_BORDER));
|
|
||||||
let digits = Vec::new();
|
let digits = Vec::new();
|
||||||
|
|
||||||
// Control buttons.
|
// Control buttons.
|
||||||
|
@ -11,7 +11,7 @@ use crate::{
|
|||||||
model_tt::{
|
model_tt::{
|
||||||
component::{
|
component::{
|
||||||
keyboard::{
|
keyboard::{
|
||||||
common::{MultiTapKeyboard, TextBox, TextEdit},
|
common::{paint_pending_marker, MultiTapKeyboard, TextBox, TextEdit},
|
||||||
mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT},
|
mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT},
|
||||||
},
|
},
|
||||||
Button, ButtonContent, ButtonMsg,
|
Button, ButtonContent, ButtonMsg,
|
||||||
@ -36,7 +36,7 @@ impl MnemonicInput for Slip39Input {
|
|||||||
/// Return the key set. Keys are further specified as indices into this
|
/// Return the key set. Keys are further specified as indices into this
|
||||||
/// array.
|
/// array.
|
||||||
fn keys() -> [&'static str; MNEMONIC_KEY_COUNT] {
|
fn keys() -> [&'static str; MNEMONIC_KEY_COUNT] {
|
||||||
["ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "xyz"]
|
["ab", "cd", "ef", "ghij", "klm", "nopq", "rs", "tuv", "wxyz"]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if given key index can continue towards a valid mnemonic
|
/// Returns `true` if given key index can continue towards a valid mnemonic
|
||||||
@ -64,6 +64,8 @@ impl MnemonicInput for Slip39Input {
|
|||||||
// Ignore the pending char rotation. We use the pending key to paint
|
// Ignore the pending char rotation. We use the pending key to paint
|
||||||
// the last character, but the mnemonic word computation depends
|
// the last character, but the mnemonic word computation depends
|
||||||
// only on the pressed key, not on the specific character inside it.
|
// only on the pressed key, not on the specific character inside it.
|
||||||
|
// Request paint of pending char.
|
||||||
|
ctx.request_paint();
|
||||||
}
|
}
|
||||||
self.complete_word_from_dictionary(ctx);
|
self.complete_word_from_dictionary(ctx);
|
||||||
}
|
}
|
||||||
@ -147,20 +149,10 @@ impl Component for Slip39Input {
|
|||||||
style.text_color,
|
style.text_color,
|
||||||
style.button_color,
|
style.button_color,
|
||||||
);
|
);
|
||||||
let width = style.font.text_width(text.as_bytes());
|
|
||||||
|
|
||||||
// Paint the pending marker.
|
// Paint the pending marker.
|
||||||
if self.multi_tap.pending_key().is_some() && self.final_word.is_none() {
|
if self.multi_tap.pending_key().is_some() && self.final_word.is_none() {
|
||||||
// Measure the width of the last character of input.
|
paint_pending_marker(text_baseline, text.as_bytes(), style.font, style.text_color);
|
||||||
if let Some(last) = text.as_bytes().last().copied() {
|
|
||||||
let last_width = style.font.text_width(&[last]);
|
|
||||||
// Draw the marker 2px under the start of the baseline of the last character.
|
|
||||||
let marker_origin = text_baseline + Offset::new(width - last_width, 2);
|
|
||||||
// Draw the marker 1px longer than the last character, and 3px thick.
|
|
||||||
let marker_rect =
|
|
||||||
Rect::from_top_left_and_size(marker_origin, Offset::new(last_width + 1, 3));
|
|
||||||
display::rect_fill(marker_rect, style.text_color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint the icon.
|
// Paint the icon.
|
||||||
@ -171,6 +163,10 @@ impl Component for Slip39Input {
|
|||||||
display::icon(icon_center, icon, style.text_color, style.button_color);
|
display::icon(icon_center, icon, style.text_color, style.button_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
self.button.bounds(sink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Slip39Input {
|
impl Slip39Input {
|
||||||
@ -200,11 +196,15 @@ impl Slip39Input {
|
|||||||
|
|
||||||
fn complete_word_from_dictionary(&mut self, ctx: &mut EventCtx) {
|
fn complete_word_from_dictionary(&mut self, ctx: &mut EventCtx) {
|
||||||
let sequence = self.input_sequence();
|
let sequence = self.input_sequence();
|
||||||
self.final_word = sequence.and_then(slip39::button_sequence_to_word);
|
|
||||||
self.input_mask = sequence
|
self.input_mask = sequence
|
||||||
.and_then(slip39::word_completion_mask)
|
.and_then(slip39::word_completion_mask)
|
||||||
.map(Slip39Mask)
|
.map(Slip39Mask)
|
||||||
.unwrap_or_else(Slip39Mask::full);
|
.unwrap_or_else(Slip39Mask::full);
|
||||||
|
self.final_word = if self.input_mask.is_final() {
|
||||||
|
sequence.and_then(slip39::button_sequence_to_word)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// Change the style of the button depending on the input.
|
// Change the style of the button depending on the input.
|
||||||
if self.final_word.is_some() {
|
if self.final_word.is_some() {
|
||||||
|
@ -5,15 +5,23 @@ mod frame;
|
|||||||
mod keyboard;
|
mod keyboard;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod page;
|
mod page;
|
||||||
|
mod scroll;
|
||||||
mod swipe;
|
mod swipe;
|
||||||
|
|
||||||
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet};
|
pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet};
|
||||||
pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
|
pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
|
||||||
pub use dialog::{Dialog, DialogLayout, DialogMsg};
|
pub use dialog::{Dialog, DialogLayout, DialogMsg};
|
||||||
pub use frame::Frame;
|
pub use frame::Frame;
|
||||||
pub use keyboard::pin::{PinKeyboard, PinKeyboardMsg};
|
pub use keyboard::{
|
||||||
|
bip39::Bip39Input,
|
||||||
|
mnemonic::{MnemonicKeyboard, MnemonicKeyboardMsg},
|
||||||
|
passphrase::{PassphraseKeyboard, PassphraseKeyboardMsg},
|
||||||
|
pin::{PinKeyboard, PinKeyboardMsg},
|
||||||
|
slip39::Slip39Input,
|
||||||
|
};
|
||||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
||||||
pub use page::SwipePage;
|
pub use page::SwipePage;
|
||||||
|
pub use scroll::ScrollBar;
|
||||||
pub use swipe::{Swipe, SwipeDirection};
|
pub use swipe::{Swipe, SwipeDirection};
|
||||||
|
|
||||||
use super::{event, theme};
|
use super::{event, theme};
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{
|
component::{
|
||||||
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Never, Pad, Paginate,
|
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Pad, Paginate,
|
||||||
},
|
},
|
||||||
display::{self, Color},
|
display::{self, Color},
|
||||||
geometry::{LinearPlacement, Offset, Rect},
|
geometry::{Offset, Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{theme, Button, Swipe, SwipeDirection};
|
use super::{theme, Button, ScrollBar, Swipe, SwipeDirection};
|
||||||
|
|
||||||
pub struct SwipePage<T, U> {
|
pub struct SwipePage<T, U> {
|
||||||
content: T,
|
content: T,
|
||||||
@ -151,8 +151,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
sink(self.scrollbar.area);
|
|
||||||
sink(self.pad.area);
|
sink(self.pad.area);
|
||||||
|
self.scrollbar.bounds(sink);
|
||||||
self.content.bounds(sink);
|
self.content.bounds(sink);
|
||||||
if !self.scrollbar.has_next_page() {
|
if !self.scrollbar.has_next_page() {
|
||||||
self.buttons.bounds(sink);
|
self.buttons.bounds(sink);
|
||||||
@ -176,117 +176,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScrollBar {
|
|
||||||
area: Rect,
|
|
||||||
page_count: usize,
|
|
||||||
active_page: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScrollBar {
|
|
||||||
const DOT_SIZE: i32 = 6;
|
|
||||||
/// Edge to edge.
|
|
||||||
const DOT_INTERVAL: i32 = 6;
|
|
||||||
/// Edge of last dot to center of arrow icon.
|
|
||||||
const ARROW_SPACE: i32 = 26;
|
|
||||||
|
|
||||||
const ICON_UP: &'static [u8] = include_res!("model_tt/res/scroll-up.toif");
|
|
||||||
const ICON_DOWN: &'static [u8] = include_res!("model_tt/res/scroll-down.toif");
|
|
||||||
|
|
||||||
pub fn vertical() -> Self {
|
|
||||||
Self {
|
|
||||||
area: Rect::zero(),
|
|
||||||
page_count: 0,
|
|
||||||
active_page: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_count_and_active_page(&mut self, page_count: usize, active_page: usize) {
|
|
||||||
self.page_count = page_count;
|
|
||||||
self.active_page = active_page;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_pages(&self) -> bool {
|
|
||||||
self.page_count > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_next_page(&self) -> bool {
|
|
||||||
self.active_page < self.page_count - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_previous_page(&self) -> bool {
|
|
||||||
self.active_page > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to_next_page(&mut self) {
|
|
||||||
self.go_to(self.active_page.saturating_add(1).min(self.page_count - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to_previous_page(&mut self) {
|
|
||||||
self.go_to(self.active_page.saturating_sub(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to(&mut self, active_page: usize) {
|
|
||||||
self.active_page = active_page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for ScrollBar {
|
|
||||||
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) {
|
|
||||||
let layout = LinearPlacement::vertical()
|
|
||||||
.align_at_center()
|
|
||||||
.with_spacing(Self::DOT_INTERVAL);
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
let mut top = None;
|
|
||||||
let mut display_icon = |top_left| {
|
|
||||||
let icon = if i == self.active_page {
|
|
||||||
theme::DOT_ACTIVE
|
|
||||||
} else {
|
|
||||||
theme::DOT_INACTIVE
|
|
||||||
};
|
|
||||||
display::icon_top_left(top_left, icon, theme::FG, theme::BG);
|
|
||||||
i += 1;
|
|
||||||
top.get_or_insert(top_left.x);
|
|
||||||
};
|
|
||||||
|
|
||||||
layout.arrange_uniform(
|
|
||||||
self.area,
|
|
||||||
self.page_count,
|
|
||||||
Offset::new(Self::DOT_SIZE, Self::DOT_SIZE),
|
|
||||||
&mut display_icon,
|
|
||||||
);
|
|
||||||
|
|
||||||
let arrow_distance = self.area.center().x - top.unwrap_or(0) + Self::ARROW_SPACE;
|
|
||||||
if self.has_previous_page() {
|
|
||||||
display::icon(
|
|
||||||
self.area.center() - Offset::y(arrow_distance),
|
|
||||||
Self::ICON_UP,
|
|
||||||
theme::FG,
|
|
||||||
theme::BG,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if self.has_next_page() {
|
|
||||||
display::icon(
|
|
||||||
self.area.center() + Offset::y(arrow_distance),
|
|
||||||
Self::ICON_DOWN,
|
|
||||||
theme::FG,
|
|
||||||
theme::BG,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PageLayout {
|
pub struct PageLayout {
|
||||||
pub content_single_page: Rect,
|
pub content_single_page: Rect,
|
||||||
pub content: Rect,
|
pub content: Rect,
|
||||||
|
138
core/embed/rust/src/ui/model_tt/component/scroll.rs
Normal file
138
core/embed/rust/src/ui/model_tt/component/scroll.rs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Component, Event, EventCtx, Never},
|
||||||
|
display,
|
||||||
|
geometry::{LinearPlacement, Offset, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::theme;
|
||||||
|
|
||||||
|
pub struct ScrollBar {
|
||||||
|
area: Rect,
|
||||||
|
layout: LinearPlacement,
|
||||||
|
arrows: bool,
|
||||||
|
pub page_count: usize,
|
||||||
|
pub active_page: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollBar {
|
||||||
|
pub const DOT_SIZE: i32 = 6;
|
||||||
|
/// Edge to edge.
|
||||||
|
const DOT_INTERVAL: i32 = 6;
|
||||||
|
/// Edge of last dot to center of arrow icon.
|
||||||
|
const ARROW_SPACE: i32 = 26;
|
||||||
|
|
||||||
|
const ICON_UP: &'static [u8] = include_res!("model_tt/res/scroll-up.toif");
|
||||||
|
const ICON_DOWN: &'static [u8] = include_res!("model_tt/res/scroll-down.toif");
|
||||||
|
|
||||||
|
fn new(layout: LinearPlacement) -> Self {
|
||||||
|
Self {
|
||||||
|
area: Rect::zero(),
|
||||||
|
layout: layout.align_at_center().with_spacing(Self::DOT_INTERVAL),
|
||||||
|
arrows: false,
|
||||||
|
page_count: 0,
|
||||||
|
active_page: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vertical() -> Self {
|
||||||
|
Self::new(LinearPlacement::vertical())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn horizontal() -> Self {
|
||||||
|
Self::new(LinearPlacement::horizontal())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_arrows(mut self) -> Self {
|
||||||
|
self.arrows = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_count_and_active_page(&mut self, page_count: usize, active_page: usize) {
|
||||||
|
self.page_count = page_count;
|
||||||
|
self.active_page = active_page;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_pages(&self) -> bool {
|
||||||
|
self.page_count > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_next_page(&self) -> bool {
|
||||||
|
self.active_page < self.page_count - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_previous_page(&self) -> bool {
|
||||||
|
self.active_page > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_next_page(&mut self) {
|
||||||
|
self.go_to(self.active_page.saturating_add(1).min(self.page_count - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_previous_page(&mut self) {
|
||||||
|
self.go_to(self.active_page.saturating_sub(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to(&mut self, active_page: usize) {
|
||||||
|
self.active_page = active_page;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ScrollBar {
|
||||||
|
type Msg = Never;
|
||||||
|
|
||||||
|
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
let mut i = 0;
|
||||||
|
let mut top = None;
|
||||||
|
let mut display_icon = |top_left| {
|
||||||
|
let icon = if i == self.active_page {
|
||||||
|
theme::DOT_ACTIVE
|
||||||
|
} else {
|
||||||
|
theme::DOT_INACTIVE
|
||||||
|
};
|
||||||
|
display::icon_top_left(top_left, icon, theme::FG, theme::BG);
|
||||||
|
i += 1;
|
||||||
|
top.get_or_insert(top_left.x);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.layout.arrange_uniform(
|
||||||
|
self.area,
|
||||||
|
self.page_count,
|
||||||
|
Offset::new(Self::DOT_SIZE, Self::DOT_SIZE),
|
||||||
|
&mut display_icon,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.arrows {
|
||||||
|
let arrow_distance = self.area.center().x - top.unwrap_or(0) + Self::ARROW_SPACE;
|
||||||
|
let offset = Offset::on_axis(self.layout.axis, arrow_distance);
|
||||||
|
if self.has_previous_page() {
|
||||||
|
display::icon(
|
||||||
|
self.area.center() - offset,
|
||||||
|
Self::ICON_UP,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if self.has_next_page() {
|
||||||
|
display::icon(
|
||||||
|
self.area.center() + offset,
|
||||||
|
Self::ICON_DOWN,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
self.area = bounds;
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||||
|
sink(self.area);
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,11 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
component::{
|
component::{
|
||||||
Button, ButtonMsg, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg, PinKeyboard,
|
Bip39Input, Button, ButtonMsg, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
|
||||||
PinKeyboardMsg, SwipePage,
|
MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg,
|
||||||
|
PinKeyboard, PinKeyboardMsg, Slip39Input, SwipePage,
|
||||||
},
|
},
|
||||||
constant, theme,
|
theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T> TryFrom<DialogMsg<T, ButtonMsg, ButtonMsg>> for Obj
|
impl<T> TryFrom<DialogMsg<T, ButtonMsg, ButtonMsg>> for Obj
|
||||||
@ -62,6 +63,27 @@ impl TryFrom<PinKeyboardMsg> for Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<MnemonicKeyboardMsg> for Obj {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(val: MnemonicKeyboardMsg) -> Result<Self, Self::Error> {
|
||||||
|
match val {
|
||||||
|
MnemonicKeyboardMsg::Confirmed => Ok(Obj::const_true()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<PassphraseKeyboardMsg> for Obj {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(val: PassphraseKeyboardMsg) -> Result<Self, Self::Error> {
|
||||||
|
match val {
|
||||||
|
PassphraseKeyboardMsg::Confirmed => Ok(Obj::const_true()),
|
||||||
|
PassphraseKeyboardMsg::Cancelled => Ok(Obj::const_none()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
extern "C" fn ui_layout_new_example(_param: Obj) -> Obj {
|
extern "C" fn ui_layout_new_example(_param: Obj) -> Obj {
|
||||||
let block = move || {
|
let block = move || {
|
||||||
@ -138,6 +160,41 @@ extern "C" fn ui_layout_new_pin(n_args: usize, args: *const Obj, kwargs: *const
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
extern "C" fn ui_layout_new_passphrase(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let _prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||||
|
let _max_len: u32 = kwargs.get(Qstr::MP_QSTR_max_len)?.try_into()?;
|
||||||
|
let obj = LayoutObj::new(PassphraseKeyboard::new().into_child())?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
extern "C" fn ui_layout_new_bip39(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let _prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
MnemonicKeyboard::new(Bip39Input::new(), b"Type word 11 of 12").into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
extern "C" fn ui_layout_new_slip39(n_args: usize, args: *const Obj, kwargs: *const Map) -> Obj {
|
||||||
|
let block = move |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let _prompt: Buffer = kwargs.get(Qstr::MP_QSTR_prompt)?.try_into()?;
|
||||||
|
let obj = LayoutObj::new(
|
||||||
|
MnemonicKeyboard::new(Slip39Input::new(), b"Type word 13 of 20").into_child(),
|
||||||
|
)?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -4,9 +4,7 @@ use crate::ui::{
|
|||||||
geometry::Insets,
|
geometry::Insets,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet};
|
||||||
component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Font constants.
|
// Font constants.
|
||||||
pub const FONT_NORMAL: Font = Font::new(-1);
|
pub const FONT_NORMAL: Font = Font::new(-1);
|
||||||
@ -101,8 +99,8 @@ pub fn button_default() -> ButtonStyleSheet {
|
|||||||
},
|
},
|
||||||
active: &ButtonStyle {
|
active: &ButtonStyle {
|
||||||
font: FONT_BOLD,
|
font: FONT_BOLD,
|
||||||
text_color: BG,
|
text_color: FG,
|
||||||
button_color: FG,
|
button_color: GREY_MEDIUM,
|
||||||
background_color: BG,
|
background_color: BG,
|
||||||
border_color: FG,
|
border_color: FG,
|
||||||
border_radius: RADIUS,
|
border_radius: RADIUS,
|
||||||
@ -290,7 +288,7 @@ pub const KEYBOARD_SPACING: i32 = 8;
|
|||||||
/// +----------+
|
/// +----------+
|
||||||
/// | 13 |
|
/// | 13 |
|
||||||
/// | +----+ |
|
/// | +----+ |
|
||||||
/// |10| | 5|
|
/// |10| |10|
|
||||||
/// | +----+ |
|
/// | +----+ |
|
||||||
/// | 14 |
|
/// | 14 |
|
||||||
/// +----------+
|
/// +----------+
|
||||||
|
@ -25,12 +25,37 @@ def layout_new_pin(
|
|||||||
*,
|
*,
|
||||||
prompt: str,
|
prompt: str,
|
||||||
subprompt: str,
|
subprompt: str,
|
||||||
danger: bool,
|
|
||||||
allow_cancel: bool,
|
allow_cancel: bool,
|
||||||
|
warning: str | None,
|
||||||
) -> object:
|
) -> object:
|
||||||
"""PIN keyboard."""
|
"""PIN keyboard."""
|
||||||
|
|
||||||
|
|
||||||
|
# extmod/rustmods/modtrezorui2.c
|
||||||
|
def layout_new_passphrase(
|
||||||
|
*,
|
||||||
|
prompt: str,
|
||||||
|
max_len: int,
|
||||||
|
) -> object:
|
||||||
|
"""Passphrase keyboard."""
|
||||||
|
|
||||||
|
|
||||||
|
# extmod/rustmods/modtrezorui2.c
|
||||||
|
def layout_new_bip39(
|
||||||
|
*,
|
||||||
|
prompt: str,
|
||||||
|
) -> object:
|
||||||
|
"""BIP39 keyboard."""
|
||||||
|
|
||||||
|
|
||||||
|
# extmod/rustmods/modtrezorui2.c
|
||||||
|
def layout_new_slip39(
|
||||||
|
*,
|
||||||
|
prompt: str,
|
||||||
|
) -> object:
|
||||||
|
"""BIP39 keyboard."""
|
||||||
|
|
||||||
|
|
||||||
# extmod/rustmods/modtrezorui2.c
|
# extmod/rustmods/modtrezorui2.c
|
||||||
def layout_new_confirm_text(
|
def layout_new_confirm_text(
|
||||||
*,
|
*,
|
||||||
|
@ -3,9 +3,15 @@ from typing import TYPE_CHECKING
|
|||||||
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
|
||||||
|
|
||||||
from trezorui2 import layout_new_confirm_action, layout_new_pin
|
from trezorui2 import (
|
||||||
|
layout_new_bip39,
|
||||||
|
layout_new_confirm_action,
|
||||||
|
layout_new_passphrase,
|
||||||
|
layout_new_pin,
|
||||||
|
layout_new_slip39,
|
||||||
|
)
|
||||||
|
|
||||||
from ...components.tt import pin
|
from ...components.tt import passphrase, pin
|
||||||
from ...constants.tt import MONO_ADDR_PER_LINE
|
from ...constants.tt import MONO_ADDR_PER_LINE
|
||||||
from ..common import button_request, interact
|
from ..common import button_request, interact
|
||||||
|
|
||||||
@ -443,7 +449,19 @@ def draw_simple_text(title: str, description: str = "") -> None:
|
|||||||
|
|
||||||
|
|
||||||
async def request_passphrase_on_device(ctx: wire.GenericContext, max_len: int) -> str:
|
async def request_passphrase_on_device(ctx: wire.GenericContext, max_len: int) -> str:
|
||||||
raise NotImplementedError
|
await button_request(
|
||||||
|
ctx, "passphrase_device", code=ButtonRequestType.PassphraseEntry
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard = _RustLayout(
|
||||||
|
layout_new_passphrase(prompt="Enter passphrase", max_len=max_len)
|
||||||
|
)
|
||||||
|
result = await ctx.wait(keyboard)
|
||||||
|
if result is passphrase.CANCELLED:
|
||||||
|
raise wire.ActionCancelled("Passphrase entry cancelled")
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def request_pin_on_device(
|
async def request_pin_on_device(
|
||||||
@ -475,3 +493,19 @@ async def request_pin_on_device(
|
|||||||
raise wire.PinCancelled
|
raise wire.PinCancelled
|
||||||
assert isinstance(result, str)
|
assert isinstance(result, str)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def request_word(
|
||||||
|
ctx: wire.GenericContext, word_index: int, word_count: int, is_slip39: bool
|
||||||
|
) -> str:
|
||||||
|
if is_slip39:
|
||||||
|
keyboard: Any = _RustLayout(
|
||||||
|
layout_new_bip39(prompt=f"Type word {word_index + 1} of {word_count}:")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
keyboard = _RustLayout(
|
||||||
|
layout_new_slip39(prompt=f"Type word {word_index + 1} of {word_count}:")
|
||||||
|
)
|
||||||
|
|
||||||
|
word: str = await ctx.wait(keyboard)
|
||||||
|
return word
|
||||||
|
Loading…
Reference in New Issue
Block a user