1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 15:38:11 +00:00

fix(core/rust/ui): pin keyboard tweaks

[no changelog]
This commit is contained in:
Martin Milata 2022-02-07 17:09:39 +01:00
parent d51072b8c1
commit efe25a6ab4
14 changed files with 426 additions and 101 deletions

View File

@ -42,6 +42,17 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_confirm_action_obj,
/// """Example layout."""
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui2_layout_new_example_obj,
ui_layout_new_example);
/// def layout_new_pin(
/// *,
/// prompt: str,
/// subprompt: str,
/// allow_cancel: bool,
/// warning: str | None,
/// ) -> object:
/// """PIN keyboard."""
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_pin_obj, 0,
ui_layout_new_pin);
#elif TREZOR_MODEL == 1
/// def layout_new_confirm_text(
/// *,
@ -62,6 +73,8 @@ STATIC const mp_rom_map_elem_t mp_module_trezorui2_globals_table[] = {
#if TREZOR_MODEL == T
{MP_ROM_QSTR(MP_QSTR_layout_new_example),
MP_ROM_PTR(&mod_trezorui2_layout_new_example_obj)},
{MP_ROM_QSTR(MP_QSTR_layout_new_pin),
MP_ROM_PTR(&mod_trezorui2_layout_new_pin_obj)},
#elif TREZOR_MODEL == 1
{MP_ROM_QSTR(MP_QSTR_layout_new_confirm_text),
MP_ROM_PTR(&mod_trezorui2_layout_new_confirm_text_obj)},

View File

@ -17,6 +17,8 @@ mp_obj_t ui_layout_new_confirm_action(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_confirm_text(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
mp_obj_t ui_layout_new_pin(size_t n_args, const mp_obj_t *args,
mp_map_t *kwargs);
#ifdef TREZOR_EMULATOR
mp_obj_t ui_debug_layout_type();

View File

@ -25,4 +25,8 @@ static void _librust_qstrs(void) {
MP_QSTR_verb;
MP_QSTR_verb_cancel;
MP_QSTR_reverse;
MP_QSTR_prompt;
MP_QSTR_subprompt;
MP_QSTR_warning;
MP_QSTR_allow_cancel;
}

View File

@ -47,6 +47,13 @@ where
pub fn text(&self) -> &T {
&self.text
}
pub fn size(&self) -> Offset {
Offset::new(
self.style.font.text_width(&self.text),
self.style.font.text_height(),
)
}
}
impl<T> Component for Label<T>
@ -56,31 +63,14 @@ where
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
let size = Offset::new(
self.style.font.text_width(&self.text),
self.style.font.line_height(),
);
self.area = match self.align {
Alignment::Start => Rect::from_top_left_and_size(bounds.top_left(), size),
Alignment::Center => {
let origin = bounds.top_left().center(bounds.top_right());
Rect {
x0: origin.x - size.x / 2,
y0: origin.y,
x1: origin.x + size.x / 2,
y1: origin.y + size.y,
}
}
Alignment::End => {
let origin = bounds.top_right();
Rect {
x0: origin.x - size.x,
y0: origin.y,
x1: origin.x,
y1: origin.y + size.y,
}
}
let origin = match self.align {
Alignment::Start => bounds.top_left(),
Alignment::Center => bounds.top_left().center(bounds.top_right()),
Alignment::End => bounds.top_right(),
};
let size = self.size();
let top_left = size.snap(origin, self.align, Alignment::Start);
self.area = Rect::from_top_left_and_size(top_left, size);
self.area
}
@ -97,4 +87,8 @@ where
self.style.background_color,
);
}
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)
}
}

View File

@ -47,6 +47,22 @@ impl Offset {
pub fn abs(self) -> Self {
Self::new(self.x.abs(), self.y.abs())
}
/// With `self` representing a rectangle size, returns top-left corner of
/// the rectangle such that it is aligned relative to the `point`.
pub fn snap(self, point: Point, x: Alignment, y: Alignment) -> Point {
let x_off = match x {
Alignment::Start => 0,
Alignment::Center => self.x / 2,
Alignment::End => self.x,
};
let y_off = match y {
Alignment::Start => 0,
Alignment::Center => self.y / 2,
Alignment::End => self.y,
};
point - Self::new(x_off, y_off)
}
}
impl Add<Offset> for Offset {
@ -280,6 +296,15 @@ impl Rect {
pub fn split_right(self, width: i32) -> (Self, Self) {
self.split_left(self.width() - width)
}
pub fn translate(&self, offset: Offset) -> Self {
Self {
x0: self.x0 + offset.x,
y0: self.y0 + offset.y,
x1: self.x1 + offset.x,
y1: self.y1 + offset.y,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]

View File

@ -61,6 +61,13 @@ impl<T> Button<T> {
}
}
pub fn initially_enabled(mut self, enabled: bool) -> Self {
if !enabled {
self.state = State::Disabled;
}
self
}
pub fn enable(&mut self, ctx: &mut EventCtx) {
self.set(ctx, State::Initial)
}

View File

@ -1,15 +1,15 @@
use core::ops::Deref;
use heapless::Vec;
use crate::{
trezorhal::random,
ui::{
component::{
base::ComponentExt,
label::{Label, LabelStyle},
Child, Component, Event, EventCtx, Never,
base::ComponentExt, Child, Component, Event, EventCtx, Label, LabelStyle, Maybe, Never,
Pad,
},
display,
geometry::{Grid, Offset, Point, Rect},
geometry::{Alignment, Grid, Insets, Offset, Rect},
model_tt::component::{
button::{Button, ButtonContent, ButtonMsg::Clicked},
theme,
@ -25,35 +25,66 @@ pub enum PinKeyboardMsg {
const MAX_LENGTH: usize = 9;
const DIGIT_COUNT: usize = 10; // 0..10
pub struct PinKeyboard {
pub struct PinKeyboard<T> {
digits: Vec<u8, MAX_LENGTH>,
major_prompt: Label<&'static [u8]>,
minor_prompt: Label<&'static [u8]>,
allow_cancel: bool,
major_prompt: Label<T>,
minor_prompt: Label<T>,
major_warning: Option<Label<T>>,
dots: Child<PinDots>,
reset_btn: Child<Button<&'static str>>,
cancel_btn: Child<Button<&'static str>>,
reset_btn: Child<Maybe<Button<&'static str>>>,
cancel_btn: Child<Maybe<Button<&'static str>>>,
confirm_btn: Child<Button<&'static str>>,
digit_btns: [Child<Button<&'static str>>; DIGIT_COUNT],
}
impl PinKeyboard {
pub fn new(major_prompt: &'static [u8], minor_prompt: &'static [u8]) -> Self {
impl<T> PinKeyboard<T>
where
T: Deref<Target = [u8]>,
{
const BUTTON_SPACING: i32 = 8;
const HEADER_HEIGHT: i32 = 25;
const HEADER_PADDING_SIDE: i32 = 5;
const HEADER_PADDING_BOTTOM: i32 = 12;
// Label position fine-tuning.
const MAJOR_OFF: Offset = Offset::y(-2);
const MINOR_OFF: Offset = Offset::y(-1);
pub fn new(
major_prompt: T,
minor_prompt: T,
major_warning: Option<T>,
allow_cancel: bool,
) -> Self {
let area = area.inset(Insets::right(theme::CONTENT_BORDER));
let digits = Vec::new();
// Control buttons.
let reset_btn = Button::with_icon(theme::ICON_BACK)
.styled(theme::button_reset())
.initially_enabled(false);
let reset_btn = Maybe::hidden(theme::BG, reset_btn).into_child();
let cancel_btn = Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel());
let cancel_btn =
Maybe::new(Pad::with_background(theme::BG), cancel_btn, allow_cancel).into_child();
Self {
major_prompt: Label::centered(major_prompt, theme::label_default()),
minor_prompt: Label::centered(minor_prompt, theme::label_default()),
dots: PinDots::new(digits.len(), theme::label_default()).into_child(),
reset_btn: Button::with_text("Reset")
.styled(theme::button_clear())
.into_child(),
cancel_btn: Button::with_icon(theme::ICON_CANCEL)
.styled(theme::button_cancel())
.into_child(),
digits,
allow_cancel,
major_prompt: Label::left_aligned(major_prompt, theme::label_keyboard()),
minor_prompt: Label::right_aligned(minor_prompt, theme::label_keyboard_minor()),
major_warning: major_warning
.map(|text| Label::left_aligned(text, theme::label_keyboard_warning())),
dots: PinDots::new(0, theme::label_default()).into_child(),
reset_btn,
cancel_btn,
confirm_btn: Button::with_icon(theme::ICON_CONFIRM)
.styled(theme::button_clear())
.styled(theme::button_confirm())
.initially_enabled(false)
.into_child(),
digit_btns: Self::generate_digit_buttons(),
digits,
}
}
@ -61,7 +92,10 @@ impl PinKeyboard {
// Generate a random sequence of digits from 0 to 9.
let mut digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
random::shuffle(&mut digits);
digits.map(Button::with_text).map(Child::new)
digits
.map(Button::with_text)
.map(|b| b.styled(theme::button_pin()))
.map(Child::new)
}
fn pin_modified(&mut self, ctx: &mut EventCtx) {
@ -70,10 +104,15 @@ impl PinKeyboard {
btn.mutate(ctx, |ctx, btn| btn.enable_if(ctx, !is_full));
}
let is_empty = self.digits.is_empty();
self.reset_btn
.mutate(ctx, |ctx, btn| btn.enable_if(ctx, !is_empty));
self.cancel_btn
.mutate(ctx, |ctx, btn| btn.enable_if(ctx, is_empty));
let cancel_enabled = is_empty && self.allow_cancel;
self.reset_btn.mutate(ctx, |ctx, btn| {
btn.show_if(ctx, !is_empty);
btn.inner_mut().enable_if(ctx, !is_empty);
});
self.cancel_btn.mutate(ctx, |ctx, btn| {
btn.show_if(ctx, cancel_enabled);
btn.inner_mut().enable_if(ctx, is_empty);
});
self.confirm_btn
.mutate(ctx, |ctx, btn| btn.enable_if(ctx, !is_empty));
let digit_count = self.digits.len();
@ -86,36 +125,49 @@ impl PinKeyboard {
}
}
impl Component for PinKeyboard {
impl<T> Component for PinKeyboard<T>
where
T: Deref<Target = [u8]>,
{
type Msg = PinKeyboardMsg;
fn place(&mut self, bounds: Rect) -> Rect {
// Prompts and PIN dots display.
let grid = if self.minor_prompt.text().is_empty() {
// Make the major prompt bigger if the minor one is empty.
Grid::new(bounds, 5, 1)
} else {
Grid::new(bounds, 6, 1)
};
self.major_prompt.place(grid.row_col(0, 0));
self.minor_prompt.place(grid.row_col(0, 1));
self.dots.place(grid.row_col(0, 0));
let (header, keypad) = bounds
.inset(theme::borders())
.split_top(Self::HEADER_HEIGHT + Self::HEADER_PADDING_BOTTOM);
let header = header.inset(Insets::new(
0,
Self::HEADER_PADDING_SIDE,
Self::HEADER_PADDING_BOTTOM,
Self::HEADER_PADDING_SIDE,
));
let major_area = header.translate(Self::MAJOR_OFF);
let minor_area = header.translate(Self::MINOR_OFF);
// Control buttons.
let grid = Grid::new(bounds, 5, 3);
self.reset_btn.place(grid.row_col(4, 0));
self.cancel_btn.place(grid.row_col(4, 0));
self.confirm_btn.place(grid.row_col(4, 2));
let grid = Grid::new(keypad, 4, 3).with_spacing(theme::KEYBOARD_SPACING);
// Prompts and PIN dots display.
self.dots.place(header);
self.major_prompt.place(major_area);
self.minor_prompt.place(minor_area);
self.major_warning.as_mut().map(|c| c.place(major_area));
// Control buttons.
let reset_cancel_area = grid.row_col(3, 0);
self.reset_btn.place(reset_cancel_area);
self.cancel_btn.place(reset_cancel_area);
self.confirm_btn.place(grid.row_col(3, 2));
// Digit buttons.
for (i, btn) in self.digit_btns.iter_mut().enumerate() {
// Assign the digits to buttons on a 5x3 grid, starting from the second row.
// Assign the digits to buttons on a 4x3 grid, starting from the first row.
let area = grid.cell(if i < 9 {
// The grid has 3 columns, and we skip the first row.
i + 3
i
} else {
// For the last key (the "0" position) we skip one cell.
i + 1 + 3
i + 1
});
btn.place(area);
}
@ -151,12 +203,17 @@ impl Component for PinKeyboard {
}
fn paint(&mut self) {
if self.digits.is_empty() {
self.cancel_btn.paint();
self.major_prompt.paint();
self.minor_prompt.paint();
} else {
self.reset_btn.paint();
if self.digits.is_empty() {
self.dots.inner().clear();
if let Some(ref mut w) = self.major_warning {
w.paint();
} else {
self.major_prompt.paint();
}
self.minor_prompt.paint();
self.cancel_btn.paint();
} else {
self.dots.paint();
}
self.confirm_btn.paint();
@ -164,6 +221,18 @@ impl Component for PinKeyboard {
btn.paint();
}
}
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.major_prompt.bounds(sink);
self.minor_prompt.bounds(sink);
self.reset_btn.bounds(sink);
self.cancel_btn.bounds(sink);
self.confirm_btn.bounds(sink);
self.dots.bounds(sink);
for b in &self.digit_btns {
b.bounds(sink)
}
}
}
struct PinDots {
@ -173,7 +242,7 @@ struct PinDots {
}
impl PinDots {
const DOT: i32 = 10;
const DOT: i32 = 6;
const PADDING: i32 = 4;
fn new(digit_count: usize, style: LabelStyle) -> Self {
@ -190,6 +259,17 @@ impl PinDots {
ctx.request_paint();
}
}
/// Clear the area with the background color.
fn clear(&self) {
display::rect_fill(self.area, self.style.background_color);
}
fn size(&self) -> Offset {
let mut width = Self::DOT * (self.digit_count as i32);
width += Self::PADDING * (self.digit_count.saturating_sub(1) as i32);
Offset::new(width, Self::DOT)
}
}
impl Component for PinDots {
@ -205,22 +285,36 @@ impl Component for PinDots {
}
fn paint(&mut self) {
// Clear the area with the background color.
display::rect_fill(self.area, self.style.background_color);
self.clear();
let mut cursor = self
.size()
.snap(self.area.center(), Alignment::Center, Alignment::Center);
// Draw a dot for each PIN digit.
for i in 0..self.digit_count {
let pos = Point {
x: self.area.x0 + i as i32 * (Self::DOT + Self::PADDING),
y: self.area.center().y,
};
let size = Offset::new(Self::DOT, Self::DOT);
display::rect_fill_rounded(
Rect::from_top_left_and_size(pos, size),
for _ in 0..self.digit_count {
display::icon_top_left(
cursor,
theme::DOT_ACTIVE,
self.style.text_color,
self.style.background_color,
4,
);
cursor.x += Self::DOT + Self::PADDING;
}
}
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area);
}
}
#[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for PinKeyboard<T>
where
T: Deref<Target = [u8]>,
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("PinKeyboard");
t.close();
}
}

View File

@ -11,6 +11,7 @@ pub use button::{Button, ButtonContent, ButtonMsg, ButtonStyle, ButtonStyleSheet
pub use confirm::{HoldToConfirm, HoldToConfirmMsg};
pub use dialog::{Dialog, DialogLayout, DialogMsg};
pub use frame::Frame;
pub use keyboard::pin::{PinKeyboard, PinKeyboardMsg};
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
pub use page::SwipePage;
pub use swipe::{Swipe, SwipeDirection};

View File

@ -189,8 +189,6 @@ impl ScrollBar {
/// Edge of last dot to center of arrow icon.
const ARROW_SPACE: i32 = 26;
const ICON_ACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-active.toif");
const ICON_INACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-inactive.toif");
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");
@ -253,9 +251,9 @@ impl Component for ScrollBar {
let mut top = None;
let mut display_icon = |top_left| {
let icon = if i == self.active_page {
Self::ICON_ACTIVE
theme::DOT_ACTIVE
} else {
Self::ICON_INACTIVE
theme::DOT_INACTIVE
};
display::icon_top_left(top_left, icon, theme::FG, theme::BG);
i += 1;

View File

@ -12,11 +12,10 @@ use crate::{
use super::{
component::{
Bip39Input, Button, ButtonMsg, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg,
MnemonicKeyboard, MnemonicKeyboardMsg, PassphraseKeyboard, PassphraseKeyboardMsg,
PinKeyboard, PinKeyboardMsg, Slip39Input, SwipePage,
Button, ButtonMsg, DialogMsg, Frame, HoldToConfirm, HoldToConfirmMsg, PinKeyboard,
PinKeyboardMsg, SwipePage,
},
theme,
constant, theme,
};
impl<T> TryFrom<DialogMsg<T, ButtonMsg, ButtonMsg>> for Obj
@ -52,6 +51,17 @@ where
}
}
impl TryFrom<PinKeyboardMsg> for Obj {
type Error = Error;
fn try_from(val: PinKeyboardMsg) -> Result<Self, Self::Error> {
match val {
PinKeyboardMsg::Confirmed => 1.try_into(),
PinKeyboardMsg::Cancelled => 2.try_into(),
}
}
}
#[no_mangle]
extern "C" fn ui_layout_new_example(_param: Obj) -> Obj {
let block = move || {
@ -112,6 +122,22 @@ extern "C" fn ui_layout_new_confirm_action(
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[no_mangle]
extern "C" fn ui_layout_new_pin(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 subprompt: Buffer = kwargs.get(Qstr::MP_QSTR_subprompt)?.try_into()?;
let allow_cancel: Option<bool> =
kwargs.get(Qstr::MP_QSTR_allow_cancel)?.try_into_option()?;
let warning: Option<Buffer> = kwargs.get(Qstr::MP_QSTR_warning)?.try_into_option()?;
let obj = LayoutObj::new(
PinKeyboard::new(prompt, subprompt, warning, allow_cancel.unwrap_or(true)).into_child(),
)?;
Ok(obj.into())
};
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
}
#[cfg(test)]
mod tests {
use crate::{

Binary file not shown.

View File

@ -27,11 +27,16 @@ pub const BLACK: Color = Color::rgb(0, 0, 0);
pub const FG: Color = WHITE; // Default foreground (text & icon) color.
pub const BG: Color = BLACK; // Default background color.
pub const RED: Color = Color::rgb(205, 73, 73); // dark-coral
pub const RED_DARK: Color = Color::rgb(166, 45, 45);
pub const YELLOW: Color = Color::rgb(193, 144, 9); // ochre
pub const YELLOW_DARK: Color = Color::rgb(154, 115, 6); // FIXME
pub const GREEN: Color = Color::rgb(57, 168, 20); // grass-green
pub const GREEN_DARK: Color = Color::rgb(48, 147, 15);
pub const BLUE: Color = Color::rgb(0, 86, 190); // blue
pub const OFF_WHITE: Color = Color::rgb(222, 222, 222); // very light grey
pub const GREY_LIGHT: Color = Color::rgb(168, 168, 168); // greyish
pub const GREY_DARK: Color = Color::rgb(51, 51, 51); // black
pub const GREY_MEDIUM: Color = Color::rgb(100, 100, 100);
pub const GREY_DARK: Color = Color::rgb(51, 51, 51); // greyer
// Commonly used corner radius (i.e. for buttons).
pub const RADIUS: u8 = 2;
@ -43,8 +48,13 @@ pub const ICON_SIZE: i32 = 16;
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_SPACE: &[u8] = include_res!("model_tt/res/space.toif");
pub const ICON_BACK: &[u8] = include_res!("model_tt/res/left.toif");
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_NEXT: &[u8] = include_res!("model_tt/res/next.toif");
// Scrollbar/PIN dots.
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 fn label_default() -> LabelStyle {
LabelStyle {
@ -54,6 +64,30 @@ pub fn label_default() -> LabelStyle {
}
}
pub fn label_keyboard() -> LabelStyle {
LabelStyle {
font: FONT_MEDIUM,
text_color: OFF_WHITE,
background_color: BG,
}
}
pub fn label_keyboard_warning() -> LabelStyle {
LabelStyle {
font: FONT_MEDIUM,
text_color: RED,
background_color: BG,
}
}
pub fn label_keyboard_minor() -> LabelStyle {
LabelStyle {
font: FONT_NORMAL,
text_color: OFF_WHITE,
background_color: BG,
}
}
pub fn button_default() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
@ -99,8 +133,8 @@ pub fn button_confirm() -> ButtonStyleSheet {
},
active: &ButtonStyle {
font: FONT_BOLD,
text_color: BG,
button_color: FG,
text_color: FG,
button_color: GREEN_DARK,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
@ -108,7 +142,7 @@ pub fn button_confirm() -> ButtonStyleSheet {
},
disabled: &ButtonStyle {
font: FONT_BOLD,
text_color: GREY_LIGHT,
text_color: FG,
button_color: GREEN,
background_color: BG,
border_color: BG,
@ -119,7 +153,99 @@ pub fn button_confirm() -> ButtonStyleSheet {
}
pub fn button_cancel() -> ButtonStyleSheet {
button_default()
ButtonStyleSheet {
normal: &ButtonStyle {
font: FONT_BOLD,
text_color: FG,
button_color: RED,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: FONT_BOLD,
text_color: FG,
button_color: RED_DARK,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: FONT_BOLD,
text_color: GREY_LIGHT,
button_color: RED,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
}
}
pub fn button_reset() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: FONT_BOLD,
text_color: FG,
button_color: YELLOW,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: FONT_BOLD,
text_color: FG,
button_color: YELLOW_DARK,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: FONT_BOLD,
text_color: GREY_LIGHT,
button_color: YELLOW,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
}
}
pub fn button_pin() -> ButtonStyleSheet {
ButtonStyleSheet {
normal: &ButtonStyle {
font: FONT_MONO,
text_color: FG,
button_color: GREY_DARK,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
active: &ButtonStyle {
font: FONT_MONO,
text_color: FG,
button_color: GREY_MEDIUM,
background_color: BG,
border_color: FG,
border_radius: RADIUS,
border_width: 0,
},
disabled: &ButtonStyle {
font: FONT_MONO,
text_color: GREY_LIGHT,
button_color: GREY_DARK,
background_color: BG,
border_color: BG,
border_radius: RADIUS,
border_width: 0,
},
}
}
pub fn button_clear() -> ButtonStyleSheet {
@ -159,6 +285,7 @@ impl DefaultTextTheme for TTDefaultText {
}
pub const CONTENT_BORDER: i32 = 5;
pub const KEYBOARD_SPACING: i32 = 8;
/// +----------+
/// | 13 |

View File

@ -20,6 +20,17 @@ def layout_new_example(text: str) -> object:
"""Example layout."""
# extmod/rustmods/modtrezorui2.c
def layout_new_pin(
*,
prompt: str,
subprompt: str,
danger: bool,
allow_cancel: bool,
) -> object:
"""PIN keyboard."""
# extmod/rustmods/modtrezorui2.c
def layout_new_confirm_text(
*,

View File

@ -3,10 +3,11 @@ from typing import TYPE_CHECKING
from trezor import io, log, loop, ui, wire, workflow
from trezor.enums import ButtonRequestType
from trezorui2 import layout_new_confirm_action
from trezorui2 import layout_new_confirm_action, layout_new_pin
from ...components.tt import pin
from ...constants.tt import MONO_ADDR_PER_LINE
from ..common import interact
from ..common import button_request, interact
if TYPE_CHECKING:
from typing import Any, Awaitable, Iterable, NoReturn, Sequence
@ -451,4 +452,26 @@ async def request_pin_on_device(
attempts_remaining: int | None,
allow_cancel: bool,
) -> str:
raise NotImplementedError
await button_request(ctx, "pin_device", code=ButtonRequestType.PinEntry)
if attempts_remaining is None:
subprompt = ""
elif attempts_remaining == 1:
subprompt = "Last attempt"
else:
subprompt = f"{attempts_remaining} tries left"
dialog = _RustLayout(
layout_new_pin(
prompt=prompt,
subprompt=subprompt,
allow_cancel=allow_cancel,
warning=None,
)
)
while True:
result = await ctx.wait(dialog)
if result is pin.CANCELLED:
raise wire.PinCancelled
assert isinstance(result, str)
return result