diff --git a/ci/build.yml b/ci/build.yml index 4097f5c157..d4bc6e10b9 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -133,22 +133,6 @@ core fw btconly production build: - firmware-T2T1-btconly-production-*.*.*-$CI_COMMIT_SHORT_SHA.bin expire_in: 1 week -core fw btconly t1 build: - stage: build - <<: *gitlab_caching - needs: [] - variables: - BITCOIN_ONLY: "1" - TREZOR_MODEL: "1" - script: - - nix-shell --run "poetry run make -C core build_firmware" - - cp core/build/firmware/firmware.bin firmware-T1B1-btconly-t1-$CORE_VERSION-$CI_COMMIT_SHORT_SHA.bin - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" - paths: - - firmware-T1B1-btconly-t1-*.*.*-$CI_COMMIT_SHORT_SHA.bin - expire_in: 1 week - # Non-frozen emulator build. This means you still need Python files # present which get interpreted. core unix regular build: @@ -289,22 +273,6 @@ core unix frozen debug build arm: tags: - docker_darwin_arm -core unix frozen btconly debug t1 build: - stage: build - <<: *gitlab_caching - needs: [] - variables: - BITCOIN_ONLY: "1" - TREZOR_MODEL: "1" - script: - - nix-shell --run "poetry run make -C core build_unix_frozen" - - mv core/build/unix/trezor-emu-core core/build/unix/trezor-emu-core-bitcoinonly - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA" - paths: - - core/build/unix # most of it needed by test_rust - expire_in: 1 week - core macos frozen regular build: stage: build <<: *gitlab_caching diff --git a/ci/test.yml b/ci/test.yml index 5c39fb9fa6..a84495c09b 100644 --- a/ci/test.yml +++ b/ci/test.yml @@ -47,17 +47,6 @@ core unit asan test: - nix-shell --run "poetry run make -C core clean build_unix | ts -s" - nix-shell --run "poetry run make -C core test_rust | ts -s" -core unit t1 test: - stage: test - <<: *gitlab_caching - needs: - - core unix frozen btconly debug t1 build - variables: - BITCOIN_ONLY: "1" - TREZOR_MODEL: "1" - script: - - nix-shell --run "poetry run make -C core test_rust | ts -s" - # Device tests for Core. Running device tests and also comparing screens # with the expected UI result. # See artifacts for a comprehensive report of UI. diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 8fe6127173..d31f989424 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -715,7 +715,9 @@ def cargo_build(): else: profile = '' - features = ['micropython', 'protobuf', f'model_t{TREZOR_MODEL.lower()}'] + # T1 does not have its own Rust feature, it shares it with TR + model_feature = 'model_tr' if TREZOR_MODEL == '1' else f'model_t{TREZOR_MODEL.lower()}' + features = ['micropython', 'protobuf', model_feature] if BITCOIN_ONLY == '1': features.append('bitcoin_only') features.append('ui') diff --git a/core/SConscript.unix b/core/SConscript.unix index 7b20042e77..1c38d63982 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -667,7 +667,9 @@ RUST_LIB = 'trezor_lib' RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a' def cargo_build(): - features = ['micropython', 'protobuf', f'model_t{TREZOR_MODEL.lower()}'] + # T1 does not have its own Rust feature, it shares it with TR + model_feature = 'model_tr' if TREZOR_MODEL == '1' else f'model_t{TREZOR_MODEL.lower()}' + features = ['micropython', 'protobuf', model_feature] if BITCOIN_ONLY == '1': features.append('bitcoin_only') features.append('ui') diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 0e85bab4e5..aab0a8c012 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -9,7 +9,6 @@ build = "build.rs" default = ["model_tt"] bitcoin_only = [] model_tt = ["touch", "jpeg"] -model_t1 = ["buttons"] model_tr = ["buttons"] micropython = [] protobuf = ["micropython"] diff --git a/core/embed/rust/src/ui/constant.rs b/core/embed/rust/src/ui/constant.rs index 2216fffe64..91fd83f704 100644 --- a/core/embed/rust/src/ui/constant.rs +++ b/core/embed/rust/src/ui/constant.rs @@ -1,12 +1,6 @@ //! Reexporting the `constant` module according to the //! current feature (Trezor model) -#[cfg(all( - feature = "model_t1", - not(feature = "model_tr"), - not(feature = "model_tt") -))] -pub use super::model_t1::constant::*; #[cfg(all(feature = "model_tr", not(feature = "model_tt")))] pub use super::model_tr::constant::*; #[cfg(feature = "model_tt")] diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index 4d792aa7ba..e7aec850f3 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -13,8 +13,6 @@ mod util; #[cfg(feature = "micropython")] pub mod layout; -#[cfg(feature = "model_t1")] -pub mod model_t1; #[cfg(feature = "model_tr")] pub mod model_tr; #[cfg(feature = "model_tt")] diff --git a/core/embed/rust/src/ui/model_t1/component/button.rs b/core/embed/rust/src/ui/model_t1/component/button.rs deleted file mode 100644 index cf9ee45d04..0000000000 --- a/core/embed/rust/src/ui/model_t1/component/button.rs +++ /dev/null @@ -1,190 +0,0 @@ -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 { - area: Rect, - pos: ButtonPos, - baseline: Point, - content: ButtonContent, - styles: ButtonStyleSheet, - state: State, -} - -impl> Button { - pub fn new(pos: ButtonPos, content: ButtonContent, 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 { - &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, - 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); - - (area, start_of_baseline) - } -} - -impl Component for Button -where - T: AsRef, -{ - 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 { - 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.negate(); - 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 crate::trace::Trace for Button -where - T: AsRef + 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 { - 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, -} diff --git a/core/embed/rust/src/ui/model_t1/component/dialog.rs b/core/embed/rust/src/ui/model_t1/component/dialog.rs deleted file mode 100644 index 39820b3b36..0000000000 --- a/core/embed/rust/src/ui/model_t1/component/dialog.rs +++ /dev/null @@ -1,94 +0,0 @@ -use super::button::{Button, ButtonMsg::Clicked}; -use crate::ui::{ - component::{Child, Component, Event, EventCtx}, - display::Font, - geometry::Rect, -}; - -pub enum DialogMsg { - Content(T), - LeftClicked, - RightClicked, -} - -pub struct Dialog { - content: Child, - left_btn: Option>>, - right_btn: Option>>, -} - -impl Dialog -where - T: Component, - U: AsRef, -{ - pub fn new(content: T, left: Option>, right: Option>) -> 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 Component for Dialog -where - T: Component, - U: AsRef, -{ - type Msg = DialogMsg; - - fn place(&mut self, bounds: Rect) -> Rect { - let button_height = 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 { - 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 crate::trace::Trace for Dialog -where - T: crate::trace::Trace, - U: crate::trace::Trace + AsRef, -{ - 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(); - } -} diff --git a/core/embed/rust/src/ui/model_t1/component/frame.rs b/core/embed/rust/src/ui/model_t1/component/frame.rs deleted file mode 100644 index 47e7a489cf..0000000000 --- a/core/embed/rust/src/ui/model_t1/component/frame.rs +++ /dev/null @@ -1,79 +0,0 @@ -use super::theme; -use crate::ui::{ - component::{Child, Component, Event, EventCtx}, - display::{self, Font}, - geometry::{Insets, Offset, Rect}, -}; - -pub struct Frame { - area: Rect, - title: U, - content: Child, -} - -impl Frame -where - T: Component, - U: AsRef, -{ - 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 Component for Frame -where - T: Component, - U: AsRef, -{ - type Msg = T::Msg; - - fn place(&mut self, bounds: Rect) -> Rect { - const TITLE_SPACE: i16 = 4; - - let (title_area, content_area) = bounds.split_top(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.content.event(ctx, event) - } - - fn paint(&mut self) { - display::text( - self.area.bottom_left() - Offset::y(2), - self.title.as_ref(), - 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 crate::trace::Trace for Frame -where - T: crate::trace::Trace, - U: crate::trace::Trace + AsRef, -{ - fn trace(&self, t: &mut dyn crate::trace::Tracer) { - t.open("Frame"); - t.field("title", &self.title); - t.field("content", &self.content); - t.close(); - } -} diff --git a/core/embed/rust/src/ui/model_t1/component/mod.rs b/core/embed/rust/src/ui/model_t1/component/mod.rs deleted file mode 100644 index dc8c10edb5..0000000000 --- a/core/embed/rust/src/ui/model_t1/component/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -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; diff --git a/core/embed/rust/src/ui/model_t1/component/page.rs b/core/embed/rust/src/ui/model_t1/component/page.rs deleted file mode 100644 index dadb410937..0000000000 --- a/core/embed/rust/src/ui/model_t1/component/page.rs +++ /dev/null @@ -1,227 +0,0 @@ -use crate::ui::{ - component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate}, - display::{self, Color, Font}, - geometry::{Insets, Offset, Point, Rect}, -}; - -use super::{theme, Button, ButtonMsg, ButtonPos}; - -pub struct ButtonPage { - content: T, - scrollbar: ScrollBar, - pad: Pad, - prev: Button<&'static str>, - next: Button<&'static str>, - cancel: Button<&'static str>, - confirm: Button<&'static str>, -} - -impl ButtonPage -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 Component for ButtonPage -where - T: Component, - T: Paginate, -{ - type Msg = PageMsg; - - fn place(&mut self, bounds: Rect) -> Rect { - let button_height = 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 { - ctx.set_page_count(self.scrollbar.page_count); - 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 crate::trace::Trace for ButtonPage -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: i16 = 8; - pub const DOT_SIZE: Offset = Offset::new(4, 4); - pub const DOT_INTERVAL: i16 = 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 { - None - } - - fn paint(&mut self) { - let count = self.page_count as i16; - 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 - } - } -} diff --git a/core/embed/rust/src/ui/model_t1/constant.rs b/core/embed/rust/src/ui/model_t1/constant.rs deleted file mode 100644 index d4de5b79bf..0000000000 --- a/core/embed/rust/src/ui/model_t1/constant.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::ui::geometry::{Offset, Point, Rect}; - -pub const WIDTH: i16 = 128; -pub const HEIGHT: i16 = 64; -pub const LINE_SPACE: i16 = 1; -pub const FONT_BPP: i16 = 1; - -pub const fn size() -> Offset { - Offset::new(WIDTH, HEIGHT) -} - -pub const fn screen() -> Rect { - Rect::from_top_left_and_size(Point::zero(), size()) -} diff --git a/core/embed/rust/src/ui/model_t1/layout.rs b/core/embed/rust/src/ui/model_t1/layout.rs deleted file mode 100644 index aefea39527..0000000000 --- a/core/embed/rust/src/ui/model_t1/layout.rs +++ /dev/null @@ -1,244 +0,0 @@ -use core::convert::TryInto; - -use crate::{ - error::Error, - micropython::{buffer::StrBuffer, map::Map, module::Module, obj::Obj, qstr::Qstr, util}, - ui::{ - component::{ - base::Component, - paginated::{PageMsg, Paginate}, - text::paragraphs::{Paragraph, Paragraphs}, - FormattedText, - }, - layout::{ - obj::{ComponentMsgObj, LayoutObj}, - result::{CANCELLED, CONFIRMED}, - util::upy_disable_animation, - }, - }, -}; - -use super::{ - component::{Button, ButtonPage, ButtonPos, Frame}, - theme, -}; - -impl ComponentMsgObj for ButtonPage -where - T: Component + Paginate, -{ - fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { - match msg { - PageMsg::Content(_) => Err(Error::TypeError), - PageMsg::Controls(true) => Ok(CONFIRMED.as_obj()), - PageMsg::Controls(false) => Ok(CANCELLED.as_obj()), - PageMsg::GoBack => unreachable!(), - } - } -} - -impl ComponentMsgObj for Frame -where - T: ComponentMsgObj, - U: AsRef, -{ - fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { - 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 = kwargs.get(Qstr::MP_QSTR_action)?.try_into_option()?; - let description: Option = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - let verb: Option = kwargs.get(Qstr::MP_QSTR_verb)?.try_into_option()?; - let verb_cancel: Option = - 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::TEXT_NORMAL, theme::FORMATTED, 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 = - kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; - - let obj = LayoutObj::new(Frame::new( - title, - ButtonPage::new( - Paragraphs::new([ - Paragraph::new(&theme::TEXT_NORMAL, description.unwrap_or_default()), - Paragraph::new(&theme::TEXT_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 disable_animation(disable: bool) -> None: - /// """Disable animations, debug builds only.""" - Qstr::MP_QSTR_disable_animation => obj_fn_1!(upy_disable_animation).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_t1::{ - 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 ComponentMsgObj for Dialog - where - T: ComponentMsgObj, - U: AsRef, - { - fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { - 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::TEXT_NORMAL, - theme::FORMATTED, - "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#" left: