mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-18 05:28:40 +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"
|
||||
|
||||
#if TREZOR_MODEL == T
|
||||
/// def layout_new_example(text: str) -> None:
|
||||
/// """Example layout."""
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui2_layout_new_example_obj,
|
||||
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[] = {
|
||||
#if TREZOR_MODEL == T
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorui2)},
|
||||
|
||||
{MP_ROM_QSTR(MP_QSTR_layout_new_example),
|
||||
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,
|
||||
|
@ -13,6 +13,8 @@ mp_obj_t protobuf_debug_msg_def_type();
|
||||
#endif
|
||||
|
||||
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
|
||||
mp_obj_t ui_debug_layout_type();
|
||||
|
@ -16,4 +16,11 @@ static void _librust_qstrs(void) {
|
||||
MP_QSTR_timer;
|
||||
MP_QSTR_paint;
|
||||
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 {
|
||||
fn from(_: TryFromIntError) -> Self {
|
||||
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) {
|
||||
display::text(
|
||||
baseline.x,
|
||||
@ -138,6 +159,10 @@ impl Color {
|
||||
pub fn to_u16(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn neg(self) -> Self {
|
||||
Self(!self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Color {
|
||||
|
@ -180,6 +180,54 @@ impl Rect {
|
||||
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)]
|
||||
|
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 {
|
||||
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 {
|
||||
0 => T1Button::Left,
|
||||
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 event;
|
||||
pub mod layout;
|
||||
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};
|
||||
|
||||
@ -19,19 +22,11 @@ pub fn button_default() -> ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: BG,
|
||||
background_color: FG,
|
||||
border_horiz: true,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: FG,
|
||||
background_color: BG,
|
||||
border_horiz: true,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: FG,
|
||||
background_color: BG,
|
||||
border_horiz: true,
|
||||
},
|
||||
}
|
||||
@ -42,20 +37,28 @@ pub fn button_cancel() -> ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: FG,
|
||||
background_color: BG,
|
||||
border_horiz: false,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: BG,
|
||||
background_color: FG,
|
||||
border_horiz: false,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: FONT_BOLD,
|
||||
text_color: BG,
|
||||
background_color: FG,
|
||||
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
|
||||
def layout_new_example(text: str) -> None:
|
||||
"""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_MAX_LINES = const(4)
|
||||
TEXT_MAX_LINES_NO_HEADER = const(5)
|
||||
PAGINATION_MARGIN_RIGHT = const(4)
|
||||
|
@ -11,7 +11,10 @@ if False:
|
||||
|
||||
|
||||
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(
|
||||
|
@ -1,6 +1,10 @@
|
||||
from trezor import ui, wire
|
||||
from trezor import log, ui, wire
|
||||
from trezor.enums import ButtonRequestType
|
||||
|
||||
from trezorui2 import layout_new_confirm_action
|
||||
|
||||
from .common import interact
|
||||
|
||||
if False:
|
||||
from typing import NoReturn, Type, Union
|
||||
|
||||
@ -16,7 +20,7 @@ async def confirm_action(
|
||||
description_param: str | None = None,
|
||||
description_param_font: int = ui.BOLD,
|
||||
verb: str | bytes | None = "OK",
|
||||
verb_cancel: str | bytes | None = "X",
|
||||
verb_cancel: str | bytes | None = "cancel",
|
||||
hold: bool = False,
|
||||
hold_danger: bool = False,
|
||||
icon: str | None = None,
|
||||
@ -26,7 +30,35 @@ async def confirm_action(
|
||||
exc: ExceptionType = wire.ActionCancelled,
|
||||
br_code: ButtonRequestType = ButtonRequestType.Other,
|
||||
) -> 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(
|
||||
@ -40,3 +72,13 @@ async def show_error_and_raise(
|
||||
exc: ExceptionType = wire.ActionCancelled,
|
||||
) -> NoReturn:
|
||||
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