mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-15 09:02:02 +00:00
feat(core/rust): add example layout for T1
[no changelog]
This commit is contained in:
parent
4d60c10330
commit
afd7cb3b01
@ -23,16 +23,37 @@
|
|||||||
|
|
||||||
#include "librust.h"
|
#include "librust.h"
|
||||||
|
|
||||||
|
#if TREZOR_MODEL == T
|
||||||
/// def layout_new_example(text: str) -> None:
|
/// def layout_new_example(text: str) -> None:
|
||||||
/// """Example layout."""
|
/// """Example layout."""
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui2_layout_new_example_obj,
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui2_layout_new_example_obj,
|
||||||
ui_layout_new_example);
|
ui_layout_new_example);
|
||||||
|
#elif TREZOR_MODEL == 1
|
||||||
|
/// def layout_new_confirm_action(
|
||||||
|
/// title: str,
|
||||||
|
/// action: str | None,
|
||||||
|
/// description: str | None,
|
||||||
|
/// verb: str | None,
|
||||||
|
/// verb_cancel: str | None,
|
||||||
|
/// hold: bool | None,
|
||||||
|
/// reverse: bool,
|
||||||
|
/// ) -> int:
|
||||||
|
/// """Example layout. All arguments must be passed as kwargs."""
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(mod_trezorui2_layout_new_confirm_action_obj,
|
||||||
|
0, ui_layout_new_confirm_action);
|
||||||
|
#endif
|
||||||
|
|
||||||
STATIC const mp_rom_map_elem_t mp_module_trezorui2_globals_table[] = {
|
STATIC const mp_rom_map_elem_t mp_module_trezorui2_globals_table[] = {
|
||||||
|
#if TREZOR_MODEL == T
|
||||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorui2)},
|
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorui2)},
|
||||||
|
|
||||||
{MP_ROM_QSTR(MP_QSTR_layout_new_example),
|
{MP_ROM_QSTR(MP_QSTR_layout_new_example),
|
||||||
MP_ROM_PTR(&mod_trezorui2_layout_new_example_obj)},
|
MP_ROM_PTR(&mod_trezorui2_layout_new_example_obj)},
|
||||||
|
#elif TREZOR_MODEL == 1
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_layout_new_confirm_action),
|
||||||
|
MP_ROM_PTR(&mod_trezorui2_layout_new_confirm_action_obj)},
|
||||||
|
#endif
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorui2_globals,
|
STATIC MP_DEFINE_CONST_DICT(mp_module_trezorui2_globals,
|
||||||
|
@ -13,6 +13,8 @@ mp_obj_t protobuf_debug_msg_def_type();
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
mp_obj_t ui_layout_new_example(mp_obj_t);
|
mp_obj_t ui_layout_new_example(mp_obj_t);
|
||||||
|
mp_obj_t ui_layout_new_confirm_action(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();
|
||||||
|
@ -16,4 +16,11 @@ static void _librust_qstrs(void) {
|
|||||||
MP_QSTR_timer;
|
MP_QSTR_timer;
|
||||||
MP_QSTR_paint;
|
MP_QSTR_paint;
|
||||||
MP_QSTR_trace;
|
MP_QSTR_trace;
|
||||||
|
|
||||||
|
MP_QSTR_title;
|
||||||
|
MP_QSTR_action;
|
||||||
|
MP_QSTR_description;
|
||||||
|
MP_QSTR_verb;
|
||||||
|
MP_QSTR_verb_cancel;
|
||||||
|
MP_QSTR_reverse;
|
||||||
}
|
}
|
||||||
|
@ -380,6 +380,23 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Obj {
|
||||||
|
/// Conversion to Rust types with typed `None`.
|
||||||
|
pub fn try_into_option<T>(self) -> Result<Option<T>, Error>
|
||||||
|
where
|
||||||
|
T: TryFrom<Obj>,
|
||||||
|
<T as TryFrom<Obj>>::Error: Into<Error>,
|
||||||
|
{
|
||||||
|
if self == Obj::const_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
match self.try_into() {
|
||||||
|
Ok(x) => Ok(Some(x)),
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<TryFromIntError> for Error {
|
impl From<TryFromIntError> for Error {
|
||||||
fn from(_: TryFromIntError) -> Self {
|
fn from(_: TryFromIntError) -> Self {
|
||||||
Self::OutOfRange
|
Self::OutOfRange
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
pub mod constants;
|
|
||||||
pub mod theme;
|
|
||||||
|
|
@ -60,6 +60,27 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used on T1 only.
|
||||||
|
pub fn rounded_rect1(r: Rect, fg_color: Color, bg_color: Color) {
|
||||||
|
display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into());
|
||||||
|
let corners = [
|
||||||
|
r.top_left(),
|
||||||
|
r.top_right() + Offset::new(-1, 0),
|
||||||
|
r.bottom_right() + Offset::new(-1, -1),
|
||||||
|
r.bottom_left() + Offset::new(0, -1),
|
||||||
|
];
|
||||||
|
for p in corners.iter() {
|
||||||
|
display::bar(p.x, p.y, 1, 1, bg_color.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used on T1 only.
|
||||||
|
pub fn dotted_line(start: Point, width: i32, color: Color) {
|
||||||
|
for x in (start.x..width).step_by(2) {
|
||||||
|
display::bar(x, start.y, 1, 1, color.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color: Color) {
|
pub fn text(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color: Color) {
|
||||||
display::text(
|
display::text(
|
||||||
baseline.x,
|
baseline.x,
|
||||||
@ -138,6 +159,10 @@ impl Color {
|
|||||||
pub fn to_u16(self) -> u16 {
|
pub fn to_u16(self) -> u16 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn neg(self) -> Self {
|
||||||
|
Self(!self.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u16> for Color {
|
impl From<u16> for Color {
|
||||||
|
@ -180,6 +180,54 @@ impl Rect {
|
|||||||
y1: self.y1,
|
y1: self.y1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hsplit(self, height: i32) -> (Self, Self) {
|
||||||
|
let height = if height.is_positive() {
|
||||||
|
height
|
||||||
|
} else {
|
||||||
|
self.height() + height
|
||||||
|
};
|
||||||
|
|
||||||
|
let top = Self {
|
||||||
|
x0: self.x0,
|
||||||
|
y0: self.y0,
|
||||||
|
x1: self.x1,
|
||||||
|
y1: self.y0 + height,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bottom = Self {
|
||||||
|
x0: self.x0,
|
||||||
|
y0: top.y0 + height,
|
||||||
|
x1: self.x1,
|
||||||
|
y1: self.y1,
|
||||||
|
};
|
||||||
|
|
||||||
|
(top, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vsplit(self, width: i32) -> (Self, Self) {
|
||||||
|
let width = if width.is_positive() {
|
||||||
|
width
|
||||||
|
} else {
|
||||||
|
self.width() + width
|
||||||
|
};
|
||||||
|
|
||||||
|
let left = Self {
|
||||||
|
x0: self.x0,
|
||||||
|
y0: self.y0,
|
||||||
|
x1: self.x0 + width,
|
||||||
|
y1: self.y1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let right = Self {
|
||||||
|
x0: left.x0 + width,
|
||||||
|
y0: self.y0,
|
||||||
|
x1: self.x1,
|
||||||
|
y1: self.y1,
|
||||||
|
};
|
||||||
|
|
||||||
|
(left, right)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
193
core/embed/rust/src/ui/model_t1/component/button.rs
Normal file
193
core/embed/rust/src/ui/model_t1/component/button.rs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Component, Event, EventCtx},
|
||||||
|
display::{self, Color, Font},
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
event::{ButtonEvent, T1Button},
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum ButtonMsg {
|
||||||
|
Clicked,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum ButtonPos {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonPos {
|
||||||
|
fn hit(&self, b: &T1Button) -> bool {
|
||||||
|
matches!(
|
||||||
|
(self, b),
|
||||||
|
(Self::Left, T1Button::Left) | (Self::Right, T1Button::Right)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Button<T> {
|
||||||
|
area: Rect,
|
||||||
|
pos: ButtonPos,
|
||||||
|
baseline: Point,
|
||||||
|
content: ButtonContent<T>,
|
||||||
|
styles: ButtonStyleSheet,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> Button<T> {
|
||||||
|
pub fn new(
|
||||||
|
area: Rect,
|
||||||
|
pos: ButtonPos,
|
||||||
|
content: ButtonContent<T>,
|
||||||
|
styles: ButtonStyleSheet,
|
||||||
|
) -> Self {
|
||||||
|
let (area, baseline) = Self::placement(area, pos, &content, &styles);
|
||||||
|
Self {
|
||||||
|
area,
|
||||||
|
pos,
|
||||||
|
baseline,
|
||||||
|
content,
|
||||||
|
styles,
|
||||||
|
state: State::Released,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_text(area: Rect, pos: ButtonPos, text: T, styles: ButtonStyleSheet) -> Self {
|
||||||
|
Self::new(area, pos, ButtonContent::Text(text), styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_icon(
|
||||||
|
area: Rect,
|
||||||
|
pos: ButtonPos,
|
||||||
|
image: &'static [u8],
|
||||||
|
styles: ButtonStyleSheet,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(area, pos, ButtonContent::Icon(image), styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content(&self) -> &ButtonContent<T> {
|
||||||
|
&self.content
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(&self) -> &ButtonStyle {
|
||||||
|
match self.state {
|
||||||
|
State::Released => self.styles.normal,
|
||||||
|
State::Pressed => self.styles.active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, ctx: &mut EventCtx, state: State) {
|
||||||
|
if self.state != state {
|
||||||
|
self.state = state;
|
||||||
|
ctx.request_paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placement(
|
||||||
|
area: Rect,
|
||||||
|
pos: ButtonPos,
|
||||||
|
content: &ButtonContent<T>,
|
||||||
|
styles: &ButtonStyleSheet,
|
||||||
|
) -> (Rect, Point) {
|
||||||
|
let border_width = if styles.normal.border_horiz { 2 } else { 0 };
|
||||||
|
let content_width = match content {
|
||||||
|
ButtonContent::Text(text) => display::text_width(text.as_ref(), styles.normal.font) - 1,
|
||||||
|
ButtonContent::Icon(_icon) => todo!(),
|
||||||
|
};
|
||||||
|
let button_width = content_width + 2 * border_width;
|
||||||
|
let area = match pos {
|
||||||
|
ButtonPos::Left => area.vsplit(button_width).0,
|
||||||
|
ButtonPos::Right => area.vsplit(-button_width).1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_of_baseline = area.bottom_left() + Offset::new(border_width, -2);
|
||||||
|
|
||||||
|
return (area, start_of_baseline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>> Component for Button<T> {
|
||||||
|
type Msg = ButtonMsg;
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
match event {
|
||||||
|
Event::Button(ButtonEvent::ButtonPressed(which)) if self.pos.hit(&which) => {
|
||||||
|
self.set(ctx, State::Pressed);
|
||||||
|
}
|
||||||
|
Event::Button(ButtonEvent::ButtonReleased(which)) if self.pos.hit(&which) => {
|
||||||
|
if matches!(self.state, State::Pressed) {
|
||||||
|
self.set(ctx, State::Released);
|
||||||
|
return Some(ButtonMsg::Clicked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
let style = self.style();
|
||||||
|
|
||||||
|
match &self.content {
|
||||||
|
ButtonContent::Text(text) => {
|
||||||
|
let background_color = style.text_color.neg();
|
||||||
|
if style.border_horiz {
|
||||||
|
display::rounded_rect1(self.area, background_color, theme::BG);
|
||||||
|
} else {
|
||||||
|
display::rect(self.area, background_color)
|
||||||
|
}
|
||||||
|
|
||||||
|
display::text(
|
||||||
|
self.baseline,
|
||||||
|
text.as_ref(),
|
||||||
|
style.font,
|
||||||
|
style.text_color,
|
||||||
|
background_color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ButtonContent::Icon(_image) => {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for Button<T>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("Button");
|
||||||
|
match &self.content {
|
||||||
|
ButtonContent::Text(text) => t.field("text", text),
|
||||||
|
ButtonContent::Icon(_) => t.symbol("icon"),
|
||||||
|
}
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
enum State {
|
||||||
|
Released,
|
||||||
|
Pressed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ButtonContent<T> {
|
||||||
|
Text(T),
|
||||||
|
Icon(&'static [u8]),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ButtonStyleSheet {
|
||||||
|
pub normal: &'static ButtonStyle,
|
||||||
|
pub active: &'static ButtonStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ButtonStyle {
|
||||||
|
pub font: Font,
|
||||||
|
pub text_color: Color,
|
||||||
|
pub border_horiz: bool,
|
||||||
|
}
|
117
core/embed/rust/src/ui/model_t1/component/dialog.rs
Normal file
117
core/embed/rust/src/ui/model_t1/component/dialog.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use super::{
|
||||||
|
button::{Button, ButtonMsg::Clicked, ButtonPos},
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
use crate::ui::{
|
||||||
|
component::{Child, Component, Event, EventCtx},
|
||||||
|
display,
|
||||||
|
geometry::{Offset, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum DialogMsg<T> {
|
||||||
|
Content(T),
|
||||||
|
LeftClicked,
|
||||||
|
RightClicked,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dialog<T, U> {
|
||||||
|
header: Option<(U, Rect)>,
|
||||||
|
content: Child<T>,
|
||||||
|
left_btn: Option<Child<Button<U>>>,
|
||||||
|
right_btn: Option<Child<Button<U>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Component, U: AsRef<[u8]>> Dialog<T, U> {
|
||||||
|
pub fn new(
|
||||||
|
area: Rect,
|
||||||
|
content: impl FnOnce(Rect) -> T,
|
||||||
|
left: Option<impl FnOnce(Rect, ButtonPos) -> Button<U>>,
|
||||||
|
right: Option<impl FnOnce(Rect, ButtonPos) -> Button<U>>,
|
||||||
|
header: Option<U>,
|
||||||
|
) -> Self {
|
||||||
|
let (header_area, content_area, button_area) = Self::areas(area, &header);
|
||||||
|
let content = Child::new(content(content_area));
|
||||||
|
let left_btn = left.map(|f| Child::new(f(button_area, ButtonPos::Left)));
|
||||||
|
let right_btn = right.map(|f| Child::new(f(button_area, ButtonPos::Right)));
|
||||||
|
Self {
|
||||||
|
header: header.zip(header_area),
|
||||||
|
content,
|
||||||
|
left_btn,
|
||||||
|
right_btn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_header(&self) {
|
||||||
|
if let Some((title, area)) = &self.header {
|
||||||
|
display::text(
|
||||||
|
area.bottom_left() + Offset::new(0, -2),
|
||||||
|
title.as_ref(),
|
||||||
|
theme::FONT_BOLD,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
);
|
||||||
|
display::dotted_line(area.bottom_left(), area.width(), theme::FG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn areas(area: Rect, header: &Option<U>) -> (Option<Rect>, Rect, Rect) {
|
||||||
|
const HEADER_SPACE: i32 = 4;
|
||||||
|
let button_height = theme::FONT_BOLD.line_height() + 2;
|
||||||
|
let header_height = theme::FONT_BOLD.line_height();
|
||||||
|
|
||||||
|
let (content_area, button_area) = area.hsplit(-button_height);
|
||||||
|
if header.is_none() {
|
||||||
|
(None, content_area, button_area)
|
||||||
|
} else {
|
||||||
|
let (header_area, content_area) = content_area.hsplit(header_height);
|
||||||
|
let (_space, content_area) = content_area.hsplit(HEADER_SPACE);
|
||||||
|
(Some(header_area), content_area, button_area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Component, U: AsRef<[u8]>> Component for Dialog<T, U> {
|
||||||
|
type Msg = DialogMsg<T::Msg>;
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
if let Some(msg) = self.content.event(ctx, event) {
|
||||||
|
Some(DialogMsg::Content(msg))
|
||||||
|
} else if let Some(Clicked) = self.left_btn.as_mut().and_then(|b| b.event(ctx, event)) {
|
||||||
|
Some(DialogMsg::LeftClicked)
|
||||||
|
} else if let Some(Clicked) = self.right_btn.as_mut().and_then(|b| b.event(ctx, event)) {
|
||||||
|
Some(DialogMsg::RightClicked)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.paint_header();
|
||||||
|
self.content.paint();
|
||||||
|
if let Some(b) = self.left_btn.as_mut() {
|
||||||
|
b.paint();
|
||||||
|
}
|
||||||
|
if let Some(b) = self.right_btn.as_mut() {
|
||||||
|
b.paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T, U> crate::trace::Trace for Dialog<T, U>
|
||||||
|
where
|
||||||
|
T: crate::trace::Trace,
|
||||||
|
U: crate::trace::Trace + AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("Dialog");
|
||||||
|
t.field("content", &self.content);
|
||||||
|
if let Some(label) = &self.left_btn {
|
||||||
|
t.field("left", label);
|
||||||
|
}
|
||||||
|
if let Some(label) = &self.right_btn {
|
||||||
|
t.field("right", label);
|
||||||
|
}
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
7
core/embed/rust/src/ui/model_t1/component/mod.rs
Normal file
7
core/embed/rust/src/ui/model_t1/component/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
mod button;
|
||||||
|
mod dialog;
|
||||||
|
|
||||||
|
use super::{event, theme};
|
||||||
|
|
||||||
|
pub use button::{Button, ButtonContent, ButtonMsg, ButtonPos, ButtonStyle, ButtonStyleSheet};
|
||||||
|
pub use dialog::{Dialog, DialogMsg};
|
@ -13,7 +13,7 @@ pub enum ButtonEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonEvent {
|
impl ButtonEvent {
|
||||||
pub fn new(event: u32, button: u32, _unused: u32) -> Result<Self, error::Error> {
|
pub fn new(event: u32, button: u32) -> Result<Self, error::Error> {
|
||||||
let button = match button {
|
let button = match button {
|
||||||
0 => T1Button::Left,
|
0 => T1Button::Left,
|
||||||
1 => T1Button::Right,
|
1 => T1Button::Right,
|
||||||
|
146
core/embed/rust/src/ui/model_t1/layout.rs
Normal file
146
core/embed/rust/src/ui/model_t1/layout.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use core::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::Error,
|
||||||
|
micropython::{buffer::Buffer, map::Map, obj::Obj, qstr::Qstr},
|
||||||
|
ui::{
|
||||||
|
component::{Child, Text},
|
||||||
|
display,
|
||||||
|
layout::obj::LayoutObj,
|
||||||
|
},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
component::{Button, Dialog, DialogMsg},
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<T> TryFrom<DialogMsg<T>> for Obj
|
||||||
|
where
|
||||||
|
Obj: TryFrom<T>,
|
||||||
|
Error: From<<T as TryInto<Obj>>::Error>,
|
||||||
|
{
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(val: DialogMsg<T>) -> Result<Self, Self::Error> {
|
||||||
|
match val {
|
||||||
|
DialogMsg::Content(c) => Ok(c.try_into()?),
|
||||||
|
DialogMsg::LeftClicked => 1.try_into(),
|
||||||
|
DialogMsg::RightClicked => 2.try_into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
extern "C" fn ui_layout_new_confirm_action(
|
||||||
|
n_args: usize,
|
||||||
|
args: *const Obj,
|
||||||
|
kwargs: *const Map,
|
||||||
|
) -> Obj {
|
||||||
|
let block = |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let title: Buffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
|
let action: Option<Buffer> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
|
||||||
|
let description: Option<Buffer> =
|
||||||
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
|
let verb: Option<Buffer> = kwargs.get(Qstr::MP_QSTR_verb)?.try_into_option()?;
|
||||||
|
let verb_cancel: Option<Buffer> =
|
||||||
|
kwargs.get(Qstr::MP_QSTR_verb_cancel)?.try_into_option()?;
|
||||||
|
let reverse: bool = kwargs.get(Qstr::MP_QSTR_reverse)?.try_into()?;
|
||||||
|
|
||||||
|
let format = match (&action, &description, reverse) {
|
||||||
|
(Some(_), Some(_), false) => "{bold}{action}\n\r{normal}{description}",
|
||||||
|
(Some(_), Some(_), true) => "{normal}{description}\n\r{bold}{action}",
|
||||||
|
(Some(_), None, _) => "{bold}{action}",
|
||||||
|
(None, Some(_), _) => "{normal}{description}",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
|
||||||
|
let left = verb_cancel
|
||||||
|
.map(|label| |area, pos| Button::with_text(area, pos, label, theme::button_cancel()));
|
||||||
|
let right = verb
|
||||||
|
.map(|label| |area, pos| Button::with_text(area, pos, label, theme::button_default()));
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(Child::new(Dialog::new(
|
||||||
|
display::screen(),
|
||||||
|
|area| {
|
||||||
|
Text::new::<theme::T1DefaultText>(area, format)
|
||||||
|
.with(b"action", action.unwrap_or("".into()))
|
||||||
|
.with(b"description", description.unwrap_or("".into()))
|
||||||
|
},
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
Some(title),
|
||||||
|
)))?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::trace::{Trace, Tracer};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl Tracer for Vec<u8> {
|
||||||
|
fn bytes(&mut self, b: &[u8]) {
|
||||||
|
self.extend(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string(&mut self, s: &str) {
|
||||||
|
self.extend(s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol(&mut self, name: &str) {
|
||||||
|
self.extend(name.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(&mut self, name: &str) {
|
||||||
|
self.extend(b"<");
|
||||||
|
self.extend(name.as_bytes());
|
||||||
|
self.extend(b" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field(&mut self, name: &str, value: &dyn Trace) {
|
||||||
|
self.extend(name.as_bytes());
|
||||||
|
self.extend(b":");
|
||||||
|
value.trace(self);
|
||||||
|
self.extend(b" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {
|
||||||
|
self.extend(b">")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trace(val: &impl Trace) -> String {
|
||||||
|
let mut t = Vec::new();
|
||||||
|
val.trace(&mut t);
|
||||||
|
String::from_utf8(t).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trace_example_layout() {
|
||||||
|
let layout = Child::new(Dialog::new(
|
||||||
|
display::screen(),
|
||||||
|
|area| {
|
||||||
|
Text::new(
|
||||||
|
area,
|
||||||
|
"Testing text layout, with some text, and some more text. And {param}",
|
||||||
|
)
|
||||||
|
.with(b"param", b"parameters!")
|
||||||
|
},
|
||||||
|
Some(|area, pos| Button::with_text(area, pos, "Left", theme::button_cancel())),
|
||||||
|
Some(|area, pos| Button::with_text(area, pos, "Right", theme::button_default())),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
trace(&layout),
|
||||||
|
r#"<Dialog content:<Text content:Testing text layout,
|
||||||
|
with some text, and
|
||||||
|
some more text. And p-
|
||||||
|
arameters! > left:<Button text:Left > right:<Button text:Right > >"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
pub mod component;
|
||||||
pub mod constant;
|
pub mod constant;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
pub mod layout;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use crate::ui::display::{Color, Font};
|
use crate::ui::{
|
||||||
|
component::text::DefaultTextTheme,
|
||||||
|
display::{Color, Font},
|
||||||
|
};
|
||||||
|
|
||||||
use super::component::{ButtonStyle, ButtonStyleSheet};
|
use super::component::{ButtonStyle, ButtonStyleSheet};
|
||||||
|
|
||||||
@ -19,19 +22,11 @@ pub fn button_default() -> ButtonStyleSheet {
|
|||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: FONT_BOLD,
|
font: FONT_BOLD,
|
||||||
text_color: BG,
|
text_color: BG,
|
||||||
background_color: FG,
|
|
||||||
border_horiz: true,
|
border_horiz: true,
|
||||||
},
|
},
|
||||||
active: &ButtonStyle {
|
active: &ButtonStyle {
|
||||||
font: FONT_BOLD,
|
font: FONT_BOLD,
|
||||||
text_color: FG,
|
text_color: FG,
|
||||||
background_color: BG,
|
|
||||||
border_horiz: true,
|
|
||||||
},
|
|
||||||
disabled: &ButtonStyle {
|
|
||||||
font: FONT_BOLD,
|
|
||||||
text_color: FG,
|
|
||||||
background_color: BG,
|
|
||||||
border_horiz: true,
|
border_horiz: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -42,20 +37,28 @@ pub fn button_cancel() -> ButtonStyleSheet {
|
|||||||
normal: &ButtonStyle {
|
normal: &ButtonStyle {
|
||||||
font: FONT_BOLD,
|
font: FONT_BOLD,
|
||||||
text_color: FG,
|
text_color: FG,
|
||||||
background_color: BG,
|
|
||||||
border_horiz: false,
|
border_horiz: false,
|
||||||
},
|
},
|
||||||
active: &ButtonStyle {
|
active: &ButtonStyle {
|
||||||
font: FONT_BOLD,
|
font: FONT_BOLD,
|
||||||
text_color: BG,
|
text_color: BG,
|
||||||
background_color: FG,
|
|
||||||
border_horiz: false,
|
|
||||||
},
|
|
||||||
disabled: &ButtonStyle {
|
|
||||||
font: FONT_BOLD,
|
|
||||||
text_color: BG,
|
|
||||||
background_color: FG,
|
|
||||||
border_horiz: false,
|
border_horiz: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum T1DefaultText {}
|
||||||
|
|
||||||
|
impl DefaultTextTheme for T1DefaultText {
|
||||||
|
const BACKGROUND_COLOR: Color = BG;
|
||||||
|
const TEXT_FONT: Font = FONT_NORMAL;
|
||||||
|
const TEXT_COLOR: Color = FG;
|
||||||
|
const HYPHEN_FONT: Font = FONT_NORMAL;
|
||||||
|
const HYPHEN_COLOR: Color = FG;
|
||||||
|
const ELLIPSIS_FONT: Font = FONT_NORMAL;
|
||||||
|
const ELLIPSIS_COLOR: Color = FG;
|
||||||
|
|
||||||
|
const NORMAL_FONT: Font = FONT_NORMAL;
|
||||||
|
const BOLD_FONT: Font = FONT_BOLD;
|
||||||
|
const MONO_FONT: Font = FONT_MONO;
|
||||||
|
}
|
||||||
|
@ -4,3 +4,16 @@ from typing import *
|
|||||||
# extmod/rustmods/modtrezorui2.c
|
# extmod/rustmods/modtrezorui2.c
|
||||||
def layout_new_example(text: str) -> None:
|
def layout_new_example(text: str) -> None:
|
||||||
"""Example layout."""
|
"""Example layout."""
|
||||||
|
|
||||||
|
|
||||||
|
# extmod/rustmods/modtrezorui2.c
|
||||||
|
def layout_new_confirm_action(
|
||||||
|
title: str,
|
||||||
|
action: str | None,
|
||||||
|
description: str | None,
|
||||||
|
verb: str | None,
|
||||||
|
verb_cancel: str | None,
|
||||||
|
hold: bool | None,
|
||||||
|
reverse: bool,
|
||||||
|
) -> int:
|
||||||
|
"""Example layout. All arguments must be passed as kwargs."""
|
||||||
|
@ -6,3 +6,4 @@ TEXT_LINE_HEIGHT_HALF = const(4)
|
|||||||
TEXT_MARGIN_LEFT = const(0)
|
TEXT_MARGIN_LEFT = const(0)
|
||||||
TEXT_MAX_LINES = const(4)
|
TEXT_MAX_LINES = const(4)
|
||||||
TEXT_MAX_LINES_NO_HEADER = const(5)
|
TEXT_MAX_LINES_NO_HEADER = const(5)
|
||||||
|
PAGINATION_MARGIN_RIGHT = const(4)
|
||||||
|
@ -11,7 +11,10 @@ if False:
|
|||||||
|
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
from ..components.tt.scroll import Paginated
|
from trezor import utils
|
||||||
|
|
||||||
|
if utils.MODEL == "T":
|
||||||
|
from ..components.tt.scroll import Paginated
|
||||||
|
|
||||||
|
|
||||||
async def button_request(
|
async def button_request(
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
from trezor import ui, wire
|
from trezor import log, ui, wire
|
||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
|
|
||||||
|
from trezorui2 import layout_new_confirm_action
|
||||||
|
|
||||||
|
from .common import interact
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
from typing import NoReturn, Type, Union
|
from typing import NoReturn, Type, Union
|
||||||
|
|
||||||
@ -16,7 +20,7 @@ async def confirm_action(
|
|||||||
description_param: str | None = None,
|
description_param: str | None = None,
|
||||||
description_param_font: int = ui.BOLD,
|
description_param_font: int = ui.BOLD,
|
||||||
verb: str | bytes | None = "OK",
|
verb: str | bytes | None = "OK",
|
||||||
verb_cancel: str | bytes | None = "X",
|
verb_cancel: str | bytes | None = "cancel",
|
||||||
hold: bool = False,
|
hold: bool = False,
|
||||||
hold_danger: bool = False,
|
hold_danger: bool = False,
|
||||||
icon: str | None = None,
|
icon: str | None = None,
|
||||||
@ -26,7 +30,35 @@ async def confirm_action(
|
|||||||
exc: ExceptionType = wire.ActionCancelled,
|
exc: ExceptionType = wire.ActionCancelled,
|
||||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||||
) -> None:
|
) -> None:
|
||||||
raise NotImplementedError
|
if isinstance(verb, bytes) or isinstance(verb_cancel, bytes):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
if description is not None and description_param is not None:
|
||||||
|
if description_param_font != ui.BOLD:
|
||||||
|
log.error(__name__, "confirm_action description_param_font not implemented")
|
||||||
|
description = description.format(description_param)
|
||||||
|
|
||||||
|
if hold:
|
||||||
|
log.error(__name__, "confirm_action hold not implemented")
|
||||||
|
|
||||||
|
result = await interact(
|
||||||
|
ctx,
|
||||||
|
ui.RustLayout(
|
||||||
|
layout_new_confirm_action(
|
||||||
|
title=title.upper(),
|
||||||
|
action=action,
|
||||||
|
description=description,
|
||||||
|
verb=verb,
|
||||||
|
verb_cancel=verb_cancel,
|
||||||
|
hold=hold,
|
||||||
|
reverse=reverse,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
br_type,
|
||||||
|
br_code,
|
||||||
|
)
|
||||||
|
if result == 1:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
async def show_error_and_raise(
|
async def show_error_and_raise(
|
||||||
@ -40,3 +72,13 @@ async def show_error_and_raise(
|
|||||||
exc: ExceptionType = wire.ActionCancelled,
|
exc: ExceptionType = wire.ActionCancelled,
|
||||||
) -> NoReturn:
|
) -> NoReturn:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
async def show_popup(
|
||||||
|
title: str,
|
||||||
|
description: str,
|
||||||
|
subtitle: str | None = None,
|
||||||
|
description_param: str = "",
|
||||||
|
timeout_ms: int = 3000,
|
||||||
|
) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
Loading…
Reference in New Issue
Block a user