mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 23:48:12 +00:00
feat(core/rust): create model_tr feature
Make event.rs shared among all features, under src/ui/event.rs. [no changelog]
This commit is contained in:
parent
12da7f301d
commit
7804893179
@ -10,6 +10,7 @@ default = ["model_tt"]
|
|||||||
bitcoin_only = []
|
bitcoin_only = []
|
||||||
model_tt = []
|
model_tt = []
|
||||||
model_t1 = []
|
model_t1 = []
|
||||||
|
model_tr = []
|
||||||
ui = []
|
ui = []
|
||||||
ui_debug = []
|
ui_debug = []
|
||||||
test = ["cc", "glob"]
|
test = ["cc", "glob"]
|
||||||
|
@ -2,15 +2,16 @@ use core::mem;
|
|||||||
|
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
|
|
||||||
#[cfg(feature = "model_t1")]
|
|
||||||
use crate::ui::model_t1::event::ButtonEvent;
|
|
||||||
#[cfg(feature = "model_tt")]
|
|
||||||
use crate::ui::model_tt::event::TouchEvent;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
ui::{component::Map, geometry::Rect},
|
ui::{component::Map, geometry::Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(any(feature = "model_t1", feature = "model_tr"))]
|
||||||
|
use crate::ui::event::ButtonEvent;
|
||||||
|
#[cfg(feature = "model_tt")]
|
||||||
|
use crate::ui::event::TouchEvent;
|
||||||
|
|
||||||
/// Type used by components that do not return any messages.
|
/// Type used by components that do not return any messages.
|
||||||
///
|
///
|
||||||
/// Alternative to the yet-unstable `!`-type.
|
/// Alternative to the yet-unstable `!`-type.
|
||||||
@ -218,7 +219,7 @@ where
|
|||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
#[cfg(feature = "model_t1")]
|
#[cfg(any(feature = "model_t1", feature = "model_tr"))]
|
||||||
Button(ButtonEvent),
|
Button(ButtonEvent),
|
||||||
#[cfg(feature = "model_tt")]
|
#[cfg(feature = "model_tt")]
|
||||||
Touch(TouchEvent),
|
Touch(TouchEvent),
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{component::LineBreaking, display::Font};
|
||||||
component::{text::layout::Op, LineBreaking},
|
|
||||||
display::Font,
|
|
||||||
geometry::Offset,
|
|
||||||
};
|
|
||||||
use core::iter;
|
use core::iter;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use crate::{micropython::time, time::Duration, trezorhal::display};
|
use crate::{micropython::time, time::Duration, trezorhal::display};
|
||||||
|
|
||||||
#[cfg(not(feature = "model_tt"))]
|
#[cfg(feature = "model_t1")]
|
||||||
use crate::ui::model_t1::constant;
|
use crate::ui::model_t1::constant;
|
||||||
|
#[cfg(feature = "model_tr")]
|
||||||
|
use crate::ui::model_tr::constant;
|
||||||
#[cfg(feature = "model_tt")]
|
#[cfg(feature = "model_tt")]
|
||||||
use crate::ui::model_tt::constant;
|
use crate::ui::model_tt::constant;
|
||||||
|
|
||||||
|
55
core/embed/rust/src/ui/event.rs
Normal file
55
core/embed/rust/src/ui/event.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use crate::{error, ui::geometry::Point};
|
||||||
|
use core::convert::TryInto;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum PhysicalButton {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ButtonEvent {
|
||||||
|
ButtonPressed(PhysicalButton),
|
||||||
|
ButtonReleased(PhysicalButton),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonEvent {
|
||||||
|
pub fn new(event: u32, button: u32) -> Result<Self, error::Error> {
|
||||||
|
let button = match button {
|
||||||
|
0 => PhysicalButton::Left,
|
||||||
|
1 => PhysicalButton::Right,
|
||||||
|
_ => return Err(error::Error::OutOfRange),
|
||||||
|
};
|
||||||
|
let result = match event {
|
||||||
|
1 => Self::ButtonPressed(button),
|
||||||
|
2 => Self::ButtonReleased(button),
|
||||||
|
_ => return Err(error::Error::OutOfRange),
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum TouchEvent {
|
||||||
|
/// A person has started touching the screen at given absolute coordinates.
|
||||||
|
/// `TouchMove` will usually follow, and `TouchEnd` should finish the
|
||||||
|
/// interaction.
|
||||||
|
TouchStart(Point),
|
||||||
|
/// Touch has moved into a different point on the screen.
|
||||||
|
TouchMove(Point),
|
||||||
|
/// Touch has ended at a point on the screen.
|
||||||
|
TouchEnd(Point),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TouchEvent {
|
||||||
|
pub fn new(event: u32, x: u32, y: u32) -> Result<Self, error::Error> {
|
||||||
|
let point = Point::new(x.try_into()?, y.try_into()?);
|
||||||
|
let result = match event {
|
||||||
|
1 => Self::TouchStart(point),
|
||||||
|
2 => Self::TouchMove(point),
|
||||||
|
4 => Self::TouchEnd(point),
|
||||||
|
_ => return Err(error::Error::OutOfRange),
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
@ -20,14 +20,15 @@ use crate::{
|
|||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(any(feature = "model_t1", feature = "model_tr"))]
|
||||||
|
use crate::ui::event::ButtonEvent;
|
||||||
#[cfg(feature = "model_tt")]
|
#[cfg(feature = "model_tt")]
|
||||||
use crate::ui::model_tt::event::TouchEvent;
|
use crate::ui::event::TouchEvent;
|
||||||
|
|
||||||
#[cfg(feature = "model_t1")]
|
#[cfg(feature = "model_t1")]
|
||||||
use crate::ui::model_t1::event::ButtonEvent;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "model_tt"))]
|
|
||||||
use crate::ui::model_t1::constant;
|
use crate::ui::model_t1::constant;
|
||||||
|
#[cfg(feature = "model_tr")]
|
||||||
|
use crate::ui::model_tr::constant;
|
||||||
#[cfg(feature = "model_tt")]
|
#[cfg(feature = "model_tt")]
|
||||||
use crate::ui::model_tt::constant;
|
use crate::ui::model_tt::constant;
|
||||||
|
|
||||||
@ -371,12 +372,12 @@ extern "C" fn ui_layout_touch_event(n_args: usize, args: *const Obj) -> Obj {
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "model_tt"))]
|
#[cfg(any(feature = "model_t1", feature = "model_tr"))]
|
||||||
extern "C" fn ui_layout_touch_event(_n_args: usize, _args: *const Obj) -> Obj {
|
extern "C" fn ui_layout_touch_event(_n_args: usize, _args: *const Obj) -> Obj {
|
||||||
Obj::const_none()
|
Obj::const_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "model_t1")]
|
#[cfg(any(feature = "model_t1", feature = "model_tr"))]
|
||||||
extern "C" fn ui_layout_button_event(n_args: usize, args: *const Obj) -> Obj {
|
extern "C" fn ui_layout_button_event(n_args: usize, args: *const Obj) -> Obj {
|
||||||
let block = |args: &[Obj], _kwargs: &Map| {
|
let block = |args: &[Obj], _kwargs: &Map| {
|
||||||
if args.len() != 3 {
|
if args.len() != 3 {
|
||||||
@ -390,7 +391,7 @@ extern "C" fn ui_layout_button_event(n_args: usize, args: *const Obj) -> Obj {
|
|||||||
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
|
unsafe { util::try_with_args_and_kwargs(n_args, args, &Map::EMPTY, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "model_t1"))]
|
#[cfg(feature = "model_tt")]
|
||||||
extern "C" fn ui_layout_button_event(_n_args: usize, _args: *const Obj) -> Obj {
|
extern "C" fn ui_layout_button_event(_n_args: usize, _args: *const Obj) -> Obj {
|
||||||
Obj::const_none()
|
Obj::const_none()
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,13 @@ pub mod macros;
|
|||||||
pub mod animation;
|
pub mod animation;
|
||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
pub mod event;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|
||||||
#[cfg(feature = "model_t1")]
|
#[cfg(feature = "model_t1")]
|
||||||
pub mod model_t1;
|
pub mod model_t1;
|
||||||
|
#[cfg(feature = "model_tr")]
|
||||||
|
pub mod model_tr;
|
||||||
#[cfg(feature = "model_tt")]
|
#[cfg(feature = "model_tt")]
|
||||||
pub mod model_tt;
|
pub mod model_tt;
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
display::{self, Color, Font},
|
display::{self, Color, Font},
|
||||||
|
event::{ButtonEvent, PhysicalButton},
|
||||||
geometry::{Offset, Point, Rect},
|
geometry::{Offset, Point, Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::theme;
|
||||||
event::{ButtonEvent, T1Button},
|
|
||||||
theme,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum ButtonMsg {
|
pub enum ButtonMsg {
|
||||||
Clicked,
|
Clicked,
|
||||||
@ -20,10 +18,10 @@ pub enum ButtonPos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonPos {
|
impl ButtonPos {
|
||||||
fn hit(&self, b: &T1Button) -> bool {
|
fn hit(&self, b: &PhysicalButton) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
(self, b),
|
(self, b),
|
||||||
(Self::Left, T1Button::Left) | (Self::Right, T1Button::Right)
|
(Self::Left, PhysicalButton::Left) | (Self::Right, PhysicalButton::Right)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ mod dialog;
|
|||||||
mod frame;
|
mod frame;
|
||||||
mod page;
|
mod page;
|
||||||
|
|
||||||
use super::{event, theme};
|
use super::theme;
|
||||||
|
|
||||||
pub use button::{Button, ButtonContent, ButtonMsg, ButtonPos, ButtonStyle, ButtonStyleSheet};
|
pub use button::{Button, ButtonContent, ButtonMsg, ButtonPos, ButtonStyle, ButtonStyleSheet};
|
||||||
pub use dialog::{Dialog, DialogMsg};
|
pub use dialog::{Dialog, DialogMsg};
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
use crate::error;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum T1Button {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum ButtonEvent {
|
|
||||||
ButtonPressed(T1Button),
|
|
||||||
ButtonReleased(T1Button),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonEvent {
|
|
||||||
pub fn new(event: u32, button: u32) -> Result<Self, error::Error> {
|
|
||||||
let button = match button {
|
|
||||||
0 => T1Button::Left,
|
|
||||||
1 => T1Button::Right,
|
|
||||||
_ => return Err(error::Error::OutOfRange),
|
|
||||||
};
|
|
||||||
let result = match event {
|
|
||||||
1 => Self::ButtonPressed(button),
|
|
||||||
2 => Self::ButtonReleased(button),
|
|
||||||
_ => return Err(error::Error::OutOfRange),
|
|
||||||
};
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod constant;
|
pub mod constant;
|
||||||
pub mod event;
|
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
190
core/embed/rust/src/ui/model_tr/component/button.rs
Normal file
190
core/embed/rust/src/ui/model_tr/component/button.rs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Component, Event, EventCtx},
|
||||||
|
display::{self, Color, Font},
|
||||||
|
event::{ButtonEvent, PhysicalButton},
|
||||||
|
geometry::{Offset, Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::theme;
|
||||||
|
|
||||||
|
pub enum ButtonMsg {
|
||||||
|
Clicked,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum ButtonPos {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonPos {
|
||||||
|
fn hit(&self, b: &PhysicalButton) -> bool {
|
||||||
|
matches!(
|
||||||
|
(self, b),
|
||||||
|
(Self::Left, PhysicalButton::Left) | (Self::Right, PhysicalButton::Right)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Button<T> {
|
||||||
|
area: Rect,
|
||||||
|
pos: ButtonPos,
|
||||||
|
baseline: Point,
|
||||||
|
content: ButtonContent<T>,
|
||||||
|
styles: ButtonStyleSheet,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> Button<T> {
|
||||||
|
pub fn new(pos: ButtonPos, content: ButtonContent<T>, styles: ButtonStyleSheet) -> Self {
|
||||||
|
Self {
|
||||||
|
pos,
|
||||||
|
content,
|
||||||
|
styles,
|
||||||
|
baseline: Point::zero(),
|
||||||
|
area: Rect::zero(),
|
||||||
|
state: State::Released,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_text(pos: ButtonPos, text: T, styles: ButtonStyleSheet) -> Self {
|
||||||
|
Self::new(pos, ButtonContent::Text(text), styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_icon(pos: ButtonPos, image: &'static [u8], styles: ButtonStyleSheet) -> Self {
|
||||||
|
Self::new(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) => styles.normal.font.text_width(text.as_ref()) - 1,
|
||||||
|
ButtonContent::Icon(_icon) => todo!(),
|
||||||
|
};
|
||||||
|
let button_width = content_width + 2 * border_width;
|
||||||
|
let area = match pos {
|
||||||
|
ButtonPos::Left => area.split_left(button_width).0,
|
||||||
|
ButtonPos::Right => area.split_right(button_width).1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_of_baseline = area.bottom_left() + Offset::new(border_width, -2);
|
||||||
|
|
||||||
|
return (area, start_of_baseline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for Button<T>
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
type Msg = ButtonMsg;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let (area, baseline) = Self::placement(bounds, self.pos, &self.content, &self.styles);
|
||||||
|
self.area = area;
|
||||||
|
self.baseline = baseline;
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
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::rect_fill_rounded1(self.area, background_color, theme::BG);
|
||||||
|
} else {
|
||||||
|
display::rect_fill(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<str> + 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,
|
||||||
|
}
|
96
core/embed/rust/src/ui/model_tr/component/dialog.rs
Normal file
96
core/embed/rust/src/ui/model_tr/component/dialog.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
use super::{
|
||||||
|
button::{Button, ButtonMsg::Clicked},
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
use crate::ui::{
|
||||||
|
component::{Child, Component, Event, EventCtx},
|
||||||
|
geometry::Rect,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum DialogMsg<T> {
|
||||||
|
Content(T),
|
||||||
|
LeftClicked,
|
||||||
|
RightClicked,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dialog<T, U> {
|
||||||
|
content: Child<T>,
|
||||||
|
left_btn: Option<Child<Button<U>>>,
|
||||||
|
right_btn: Option<Child<Button<U>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Dialog<T, U>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
U: AsRef<str>,
|
||||||
|
{
|
||||||
|
pub fn new(content: T, left: Option<Button<U>>, right: Option<Button<U>>) -> Self {
|
||||||
|
Self {
|
||||||
|
content: Child::new(content),
|
||||||
|
left_btn: left.map(Child::new),
|
||||||
|
right_btn: right.map(Child::new),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &T {
|
||||||
|
self.content.inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Component for Dialog<T, U>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
U: AsRef<str>,
|
||||||
|
{
|
||||||
|
type Msg = DialogMsg<T::Msg>;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let button_height = theme::FONT_BOLD.line_height() + 2;
|
||||||
|
let (content_area, button_area) = bounds.split_bottom(button_height);
|
||||||
|
self.content.place(content_area);
|
||||||
|
self.left_btn.as_mut().map(|b| b.place(button_area));
|
||||||
|
self.right_btn.as_mut().map(|b| b.place(button_area));
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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<str>,
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
79
core/embed/rust/src/ui/model_tr/component/frame.rs
Normal file
79
core/embed/rust/src/ui/model_tr/component/frame.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use super::theme;
|
||||||
|
use crate::ui::{
|
||||||
|
component::{Child, Component, Event, EventCtx},
|
||||||
|
display,
|
||||||
|
geometry::{Insets, Offset, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Frame<T, U> {
|
||||||
|
area: Rect,
|
||||||
|
title: U,
|
||||||
|
content: Child<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Frame<T, U>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
U: AsRef<str>,
|
||||||
|
{
|
||||||
|
pub fn new(title: U, content: T) -> Self {
|
||||||
|
Self {
|
||||||
|
title,
|
||||||
|
area: Rect::zero(),
|
||||||
|
content: Child::new(content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner(&self) -> &T {
|
||||||
|
self.content.inner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Component for Frame<T, U>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
U: AsRef<str>,
|
||||||
|
{
|
||||||
|
type Msg = T::Msg;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
const TITLE_SPACE: i32 = 4;
|
||||||
|
|
||||||
|
let (title_area, content_area) = bounds.split_top(theme::FONT_BOLD.line_height());
|
||||||
|
let content_area = content_area.inset(Insets::top(TITLE_SPACE));
|
||||||
|
|
||||||
|
self.area = title_area;
|
||||||
|
self.content.place(content_area);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
self.content.event(ctx, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
display::text(
|
||||||
|
self.area.bottom_left() - Offset::y(2),
|
||||||
|
self.title.as_ref(),
|
||||||
|
theme::FONT_BOLD,
|
||||||
|
theme::FG,
|
||||||
|
theme::BG,
|
||||||
|
);
|
||||||
|
display::dotted_line(self.area.bottom_left(), self.area.width(), theme::FG);
|
||||||
|
self.content.paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T, U> crate::trace::Trace for Frame<T, U>
|
||||||
|
where
|
||||||
|
T: crate::trace::Trace,
|
||||||
|
U: crate::trace::Trace + AsRef<str>,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("Frame");
|
||||||
|
t.field("title", &self.title);
|
||||||
|
t.field("content", &self.content);
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
11
core/embed/rust/src/ui/model_tr/component/mod.rs
Normal file
11
core/embed/rust/src/ui/model_tr/component/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
mod button;
|
||||||
|
mod dialog;
|
||||||
|
mod frame;
|
||||||
|
mod page;
|
||||||
|
|
||||||
|
use super::theme;
|
||||||
|
|
||||||
|
pub use button::{Button, ButtonContent, ButtonMsg, ButtonPos, ButtonStyle, ButtonStyleSheet};
|
||||||
|
pub use dialog::{Dialog, DialogMsg};
|
||||||
|
pub use frame::Frame;
|
||||||
|
pub use page::ButtonPage;
|
226
core/embed/rust/src/ui/model_tr/component/page.rs
Normal file
226
core/embed/rust/src/ui/model_tr/component/page.rs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate},
|
||||||
|
display::{self, Color},
|
||||||
|
geometry::{Insets, Offset, Point, Rect},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{theme, Button, ButtonMsg, ButtonPos};
|
||||||
|
|
||||||
|
pub struct ButtonPage<T> {
|
||||||
|
content: T,
|
||||||
|
scrollbar: ScrollBar,
|
||||||
|
pad: Pad,
|
||||||
|
prev: Button<&'static str>,
|
||||||
|
next: Button<&'static str>,
|
||||||
|
cancel: Button<&'static str>,
|
||||||
|
confirm: Button<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ButtonPage<T>
|
||||||
|
where
|
||||||
|
T: Paginate,
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
pub fn new(content: T, background: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
content,
|
||||||
|
scrollbar: ScrollBar::vertical(),
|
||||||
|
pad: Pad::with_background(background),
|
||||||
|
prev: Button::with_text(ButtonPos::Left, "BACK", theme::button_cancel()),
|
||||||
|
next: Button::with_text(ButtonPos::Right, "NEXT", theme::button_default()),
|
||||||
|
cancel: Button::with_text(ButtonPos::Left, "CANCEL", theme::button_cancel()),
|
||||||
|
confirm: Button::with_text(ButtonPos::Right, "CONFIRM", theme::button_default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_page(&mut self, ctx: &mut EventCtx, page: usize) {
|
||||||
|
// Change the page in the content, clear the background under it and make sure
|
||||||
|
// it gets completely repainted.
|
||||||
|
self.content.change_page(page);
|
||||||
|
self.content.request_complete_repaint(ctx);
|
||||||
|
self.pad.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for ButtonPage<T>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
T: Paginate,
|
||||||
|
{
|
||||||
|
type Msg = PageMsg<T::Msg, bool>;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let button_height = theme::FONT_BOLD.line_height() + 2;
|
||||||
|
let (content_area, button_area) = bounds.split_bottom(button_height);
|
||||||
|
let (content_area, scrollbar_area) = content_area.split_right(ScrollBar::WIDTH);
|
||||||
|
let content_area = content_area.inset(Insets::top(1));
|
||||||
|
self.pad.place(bounds);
|
||||||
|
self.content.place(content_area);
|
||||||
|
let page_count = self.content.page_count();
|
||||||
|
self.scrollbar.set_count_and_active_page(page_count, 0);
|
||||||
|
self.scrollbar.place(scrollbar_area);
|
||||||
|
self.prev.place(button_area);
|
||||||
|
self.next.place(button_area);
|
||||||
|
self.cancel.place(button_area);
|
||||||
|
self.confirm.place(button_area);
|
||||||
|
bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
if self.scrollbar.has_previous_page() {
|
||||||
|
if let Some(ButtonMsg::Clicked) = self.prev.event(ctx, event) {
|
||||||
|
// Scroll up.
|
||||||
|
self.scrollbar.go_to_previous_page();
|
||||||
|
self.change_page(ctx, self.scrollbar.active_page);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else if let Some(ButtonMsg::Clicked) = self.cancel.event(ctx, event) {
|
||||||
|
return Some(PageMsg::Controls(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.scrollbar.has_next_page() {
|
||||||
|
if let Some(ButtonMsg::Clicked) = self.next.event(ctx, event) {
|
||||||
|
// Scroll down.
|
||||||
|
self.scrollbar.go_to_next_page();
|
||||||
|
self.change_page(ctx, self.scrollbar.active_page);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else if let Some(ButtonMsg::Clicked) = self.confirm.event(ctx, event) {
|
||||||
|
return Some(PageMsg::Controls(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(msg) = self.content.event(ctx, event) {
|
||||||
|
return Some(PageMsg::Content(msg));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
self.pad.paint();
|
||||||
|
self.content.paint();
|
||||||
|
self.scrollbar.paint();
|
||||||
|
if self.scrollbar.has_previous_page() {
|
||||||
|
self.prev.paint();
|
||||||
|
} else {
|
||||||
|
self.cancel.paint();
|
||||||
|
}
|
||||||
|
if self.scrollbar.has_next_page() {
|
||||||
|
self.next.paint();
|
||||||
|
} else {
|
||||||
|
self.confirm.paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for ButtonPage<T>
|
||||||
|
where
|
||||||
|
T: crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.open("ButtonPage");
|
||||||
|
t.field("active_page", &self.scrollbar.active_page);
|
||||||
|
t.field("page_count", &self.scrollbar.page_count);
|
||||||
|
t.field("content", &self.content);
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScrollBar {
|
||||||
|
area: Rect,
|
||||||
|
page_count: usize,
|
||||||
|
active_page: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollBar {
|
||||||
|
pub const WIDTH: i32 = 8;
|
||||||
|
pub const DOT_SIZE: Offset = Offset::new(4, 4);
|
||||||
|
pub const DOT_INTERVAL: i32 = 6;
|
||||||
|
|
||||||
|
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_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.active_page = self.active_page.saturating_add(1).min(self.page_count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_previous_page(&mut self) {
|
||||||
|
self.active_page = self.active_page.saturating_sub(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_dot(&self, active: bool, top_left: Point) {
|
||||||
|
let sides = [
|
||||||
|
Rect::from_top_left_and_size(top_left + Offset::x(1), Offset::new(2, 1)),
|
||||||
|
Rect::from_top_left_and_size(top_left + Offset::y(1), Offset::new(1, 2)),
|
||||||
|
Rect::from_top_left_and_size(
|
||||||
|
top_left + Offset::new(1, Self::DOT_SIZE.y - 1),
|
||||||
|
Offset::new(2, 1),
|
||||||
|
),
|
||||||
|
Rect::from_top_left_and_size(
|
||||||
|
top_left + Offset::new(Self::DOT_SIZE.x - 1, 1),
|
||||||
|
Offset::new(1, 2),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for side in sides {
|
||||||
|
display::rect_fill(side, theme::FG)
|
||||||
|
}
|
||||||
|
if active {
|
||||||
|
display::rect_fill(
|
||||||
|
Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(Insets::uniform(1)),
|
||||||
|
theme::FG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 count = self.page_count as i32;
|
||||||
|
let interval = {
|
||||||
|
let available_height = self.area.height();
|
||||||
|
let naive_height = count * Self::DOT_INTERVAL;
|
||||||
|
if naive_height > available_height {
|
||||||
|
available_height / count
|
||||||
|
} else {
|
||||||
|
Self::DOT_INTERVAL
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut dot = Point::new(
|
||||||
|
self.area.center().x - Self::DOT_SIZE.x / 2,
|
||||||
|
self.area.center().y - (count / 2) * interval,
|
||||||
|
);
|
||||||
|
for i in 0..self.page_count {
|
||||||
|
self.paint_dot(i == self.active_page, dot);
|
||||||
|
dot.y += interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
core/embed/rust/src/ui/model_tr/constant.rs
Normal file
13
core/embed/rust/src/ui/model_tr/constant.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use crate::ui::geometry::{Offset, Point, Rect};
|
||||||
|
|
||||||
|
pub const WIDTH: i32 = 128;
|
||||||
|
pub const HEIGHT: i32 = 128;
|
||||||
|
pub const LINE_SPACE: i32 = 1;
|
||||||
|
|
||||||
|
pub const fn size() -> Offset {
|
||||||
|
Offset::new(WIDTH, HEIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn screen() -> Rect {
|
||||||
|
Rect::from_top_left_and_size(Point::zero(), size())
|
||||||
|
}
|
237
core/embed/rust/src/ui/model_tr/layout.rs
Normal file
237
core/embed/rust/src/ui/model_tr/layout.rs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
use core::convert::TryInto;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::Error,
|
||||||
|
micropython::{buffer::StrBuffer, map::Map, module::Module, obj::Obj, qstr::Qstr},
|
||||||
|
ui::{
|
||||||
|
component::{
|
||||||
|
base::Component,
|
||||||
|
paginated::{PageMsg, Paginate},
|
||||||
|
text::paragraphs::Paragraphs,
|
||||||
|
FormattedText,
|
||||||
|
},
|
||||||
|
layout::{
|
||||||
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
|
result::{CANCELLED, CONFIRMED},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
component::{Button, ButtonPage, ButtonPos, Frame},
|
||||||
|
theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<T> ComponentMsgObj for ButtonPage<T>
|
||||||
|
where
|
||||||
|
T: Component + Paginate,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
PageMsg::Content(_) => Err(Error::TypeError),
|
||||||
|
PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()),
|
||||||
|
PageMsg::Controls(false) => Ok(CANCELLED.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> ComponentMsgObj for Frame<T, U>
|
||||||
|
where
|
||||||
|
T: ComponentMsgObj,
|
||||||
|
U: AsRef<str>,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
self.inner().msg_try_into_obj(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
|
let action: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?;
|
||||||
|
let description: Option<StrBuffer> =
|
||||||
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
|
let verb: Option<StrBuffer> = kwargs.get(Qstr::MP_QSTR_verb)?.try_into_option()?;
|
||||||
|
let verb_cancel: Option<StrBuffer> =
|
||||||
|
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| Button::with_text(ButtonPos::Left, label, theme::button_cancel()));
|
||||||
|
let _right =
|
||||||
|
verb.map(|label| Button::with_text(ButtonPos::Right, label, theme::button_default()));
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(Frame::new(
|
||||||
|
title,
|
||||||
|
ButtonPage::new(
|
||||||
|
FormattedText::new::<theme::TRDefaultText>(format)
|
||||||
|
.with("action", action.unwrap_or_default())
|
||||||
|
.with("description", description.unwrap_or_default()),
|
||||||
|
theme::BG,
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn new_confirm_text(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
|
||||||
|
let block = |_args: &[Obj], kwargs: &Map| {
|
||||||
|
let title: StrBuffer = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
|
let data: StrBuffer = kwargs.get(Qstr::MP_QSTR_data)?.try_into()?;
|
||||||
|
let description: Option<StrBuffer> =
|
||||||
|
kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?;
|
||||||
|
|
||||||
|
let obj = LayoutObj::new(Frame::new(
|
||||||
|
title,
|
||||||
|
ButtonPage::new(
|
||||||
|
Paragraphs::new()
|
||||||
|
.add::<theme::TRDefaultText>(
|
||||||
|
theme::FONT_NORMAL,
|
||||||
|
description.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.add::<theme::TRDefaultText>(theme::FONT_BOLD, data),
|
||||||
|
theme::BG,
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
Ok(obj.into())
|
||||||
|
};
|
||||||
|
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub static mp_module_trezorui2: Module = obj_module! {
|
||||||
|
Qstr::MP_QSTR___name__ => Qstr::MP_QSTR_trezorui2.to_obj(),
|
||||||
|
|
||||||
|
/// CONFIRMED: object
|
||||||
|
Qstr::MP_QSTR_CONFIRMED => CONFIRMED.as_obj(),
|
||||||
|
|
||||||
|
/// CANCELLED: object
|
||||||
|
Qstr::MP_QSTR_CANCELLED => CANCELLED.as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_action(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// action: str | None = None,
|
||||||
|
/// description: str | None = None,
|
||||||
|
/// verb: str | None = None,
|
||||||
|
/// verb_cancel: str | None = None,
|
||||||
|
/// hold: bool | None = None,
|
||||||
|
/// reverse: bool = False,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm action."""
|
||||||
|
Qstr::MP_QSTR_confirm_action => obj_fn_kw!(0, new_confirm_action).as_obj(),
|
||||||
|
|
||||||
|
/// def confirm_text(
|
||||||
|
/// *,
|
||||||
|
/// title: str,
|
||||||
|
/// data: str,
|
||||||
|
/// description: str | None,
|
||||||
|
/// ) -> object:
|
||||||
|
/// """Confirm text."""
|
||||||
|
Qstr::MP_QSTR_confirm_text => obj_fn_kw!(0, new_confirm_text).as_obj(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
trace::Trace,
|
||||||
|
ui::{
|
||||||
|
component::Component,
|
||||||
|
model_tr::{
|
||||||
|
component::{Dialog, DialogMsg},
|
||||||
|
constant,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn trace(val: &impl Trace) -> String {
|
||||||
|
let mut t = Vec::new();
|
||||||
|
val.trace(&mut t);
|
||||||
|
String::from_utf8(t).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> ComponentMsgObj for Dialog<T, U>
|
||||||
|
where
|
||||||
|
T: ComponentMsgObj,
|
||||||
|
U: AsRef<str>,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
DialogMsg::Content(c) => self.inner().msg_try_into_obj(c),
|
||||||
|
DialogMsg::LeftClicked => Ok(CANCELLED.as_obj()),
|
||||||
|
DialogMsg::RightClicked => Ok(CONFIRMED.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trace_example_layout() {
|
||||||
|
let mut layout = Dialog::new(
|
||||||
|
FormattedText::new::<theme::TRDefaultText>(
|
||||||
|
"Testing text layout, with some text, and some more text. And {param}",
|
||||||
|
)
|
||||||
|
.with("param", "parameters!"),
|
||||||
|
Some(Button::with_text(
|
||||||
|
ButtonPos::Left,
|
||||||
|
"Left",
|
||||||
|
theme::button_cancel(),
|
||||||
|
)),
|
||||||
|
Some(Button::with_text(
|
||||||
|
ButtonPos::Right,
|
||||||
|
"Right",
|
||||||
|
theme::button_default(),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
layout.place(constant::screen());
|
||||||
|
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 > >"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trace_layout_title() {
|
||||||
|
let mut layout = Frame::new(
|
||||||
|
"Please confirm",
|
||||||
|
Dialog::new(
|
||||||
|
FormattedText::new::<theme::TRDefaultText>(
|
||||||
|
"Testing text layout, with some text, and some more text. And {param}",
|
||||||
|
)
|
||||||
|
.with("param", "parameters!"),
|
||||||
|
Some(Button::with_text(
|
||||||
|
ButtonPos::Left,
|
||||||
|
"Left",
|
||||||
|
theme::button_cancel(),
|
||||||
|
)),
|
||||||
|
Some(Button::with_text(
|
||||||
|
ButtonPos::Right,
|
||||||
|
"Right",
|
||||||
|
theme::button_default(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
layout.place(constant::screen());
|
||||||
|
assert_eq!(
|
||||||
|
trace(&layout),
|
||||||
|
r#"<Frame title:Please confirm content:<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 > > >"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
4
core/embed/rust/src/ui/model_tr/mod.rs
Normal file
4
core/embed/rust/src/ui/model_tr/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod component;
|
||||||
|
pub mod constant;
|
||||||
|
pub mod layout;
|
||||||
|
pub mod theme;
|
66
core/embed/rust/src/ui/model_tr/theme.rs
Normal file
66
core/embed/rust/src/ui/model_tr/theme.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use crate::ui::{
|
||||||
|
component::text::layout::DefaultTextTheme,
|
||||||
|
display::{Color, Font},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::component::{ButtonStyle, ButtonStyleSheet};
|
||||||
|
|
||||||
|
// Font constants.
|
||||||
|
pub const FONT_NORMAL: Font = Font::new(-1);
|
||||||
|
pub const FONT_MEDIUM: Font = Font::new(-5);
|
||||||
|
pub const FONT_BOLD: Font = Font::new(-2);
|
||||||
|
pub const FONT_MONO: Font = Font::new(-3);
|
||||||
|
|
||||||
|
// Color palette.
|
||||||
|
pub const WHITE: Color = Color::rgb(255, 255, 255);
|
||||||
|
pub const BLACK: Color = Color::rgb(0, 0, 0);
|
||||||
|
pub const GREY_LIGHT: Color = WHITE; // Word/page break characters.
|
||||||
|
pub const FG: Color = WHITE; // Default foreground (text & icon) color.
|
||||||
|
pub const BG: Color = BLACK; // Default background color.
|
||||||
|
|
||||||
|
pub fn button_default() -> ButtonStyleSheet {
|
||||||
|
ButtonStyleSheet {
|
||||||
|
normal: &ButtonStyle {
|
||||||
|
font: FONT_BOLD,
|
||||||
|
text_color: BG,
|
||||||
|
border_horiz: true,
|
||||||
|
},
|
||||||
|
active: &ButtonStyle {
|
||||||
|
font: FONT_BOLD,
|
||||||
|
text_color: FG,
|
||||||
|
border_horiz: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn button_cancel() -> ButtonStyleSheet {
|
||||||
|
ButtonStyleSheet {
|
||||||
|
normal: &ButtonStyle {
|
||||||
|
font: FONT_BOLD,
|
||||||
|
text_color: FG,
|
||||||
|
border_horiz: false,
|
||||||
|
},
|
||||||
|
active: &ButtonStyle {
|
||||||
|
font: FONT_BOLD,
|
||||||
|
text_color: BG,
|
||||||
|
border_horiz: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TRDefaultText;
|
||||||
|
|
||||||
|
impl DefaultTextTheme for TRDefaultText {
|
||||||
|
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 MEDIUM_FONT: Font = FONT_MEDIUM;
|
||||||
|
const BOLD_FONT: Font = FONT_BOLD;
|
||||||
|
const MONO_FONT: Font = FONT_MONO;
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, ComponentExt, Event, EventCtx, GridPlaced, Map},
|
component::{Component, ComponentExt, Event, EventCtx, GridPlaced, Map},
|
||||||
display::{self, Color, Font},
|
display::{self, Color, Font},
|
||||||
|
event::TouchEvent,
|
||||||
geometry::{Insets, Offset, Rect},
|
geometry::{Insets, Offset, Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{event::TouchEvent, theme};
|
use super::theme;
|
||||||
|
|
||||||
pub enum ButtonMsg {
|
pub enum ButtonMsg {
|
||||||
Pressed,
|
Pressed,
|
||||||
|
@ -24,4 +24,4 @@ pub use page::SwipePage;
|
|||||||
pub use scroll::ScrollBar;
|
pub use scroll::ScrollBar;
|
||||||
pub use swipe::{Swipe, SwipeDirection};
|
pub use swipe::{Swipe, SwipeDirection};
|
||||||
|
|
||||||
use super::{event, theme};
|
use super::theme;
|
||||||
|
@ -213,8 +213,9 @@ mod tests {
|
|||||||
trace::Trace,
|
trace::Trace,
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::paragraphs::Paragraphs, Empty},
|
component::{text::paragraphs::Paragraphs, Empty},
|
||||||
|
event::TouchEvent,
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
model_tt::{constant, event::TouchEvent, theme},
|
model_tt::{constant, theme},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
component::{Component, Event, EventCtx},
|
component::{Component, Event, EventCtx},
|
||||||
display,
|
display,
|
||||||
|
event::TouchEvent,
|
||||||
geometry::{Point, Rect},
|
geometry::{Point, Rect},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{event::TouchEvent, theme};
|
use super::theme;
|
||||||
|
|
||||||
pub enum SwipeDirection {
|
pub enum SwipeDirection {
|
||||||
Up,
|
Up,
|
||||||
|
@ -27,6 +27,32 @@ def confirm_text(
|
|||||||
"""Confirm text."""
|
"""Confirm text."""
|
||||||
CONFIRMED: object
|
CONFIRMED: object
|
||||||
CANCELLED: object
|
CANCELLED: object
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tr/layout.rs
|
||||||
|
def confirm_action(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
action: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
|
verb: str | None = None,
|
||||||
|
verb_cancel: str | None = None,
|
||||||
|
hold: bool | None = None,
|
||||||
|
reverse: bool = False,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm action."""
|
||||||
|
|
||||||
|
|
||||||
|
# rust/src/ui/model_tr/layout.rs
|
||||||
|
def confirm_text(
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
data: str,
|
||||||
|
description: str | None,
|
||||||
|
) -> object:
|
||||||
|
"""Confirm text."""
|
||||||
|
CONFIRMED: object
|
||||||
|
CANCELLED: object
|
||||||
INFO: object
|
INFO: object
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user