From 7626a99a0f170c22835d47a32abf2586c69ffc9e Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Wed, 19 Oct 2022 23:03:59 +0200 Subject: [PATCH] feat(core/rust): model R bootloader implementation in rust --- .../src/ui/model_tr/bootloader/confirm.rs | 116 ++++++ .../rust/src/ui/model_tr/bootloader/intro.rs | 110 +++++ .../rust/src/ui/model_tr/bootloader/menu.rs | 104 +++++ .../rust/src/ui/model_tr/bootloader/mod.rs | 393 ++++++++++++++++++ .../rust/src/ui/model_tr/bootloader/theme.rs | 50 +++ .../rust/src/ui/model_tr/bootloader/title.rs | 52 +++ .../rust/src/ui/model_tr/component/mod.rs | 2 + .../rust/src/ui/model_tr/component/result.rs | 84 ++++ core/embed/rust/src/ui/model_tr/constant.rs | 2 + core/embed/rust/src/ui/model_tr/mod.rs | 3 + .../src/ui/model_tr/res/trezor_empty.toif | Bin 0 -> 398 bytes core/embed/rust/src/ui/model_tr/screens.rs | 76 ++++ .../rust/src/ui/model_tt/bootloader/mod.rs | 2 +- 13 files changed, 993 insertions(+), 1 deletion(-) create mode 100644 core/embed/rust/src/ui/model_tr/bootloader/confirm.rs create mode 100644 core/embed/rust/src/ui/model_tr/bootloader/intro.rs create mode 100644 core/embed/rust/src/ui/model_tr/bootloader/menu.rs create mode 100644 core/embed/rust/src/ui/model_tr/bootloader/mod.rs create mode 100644 core/embed/rust/src/ui/model_tr/bootloader/theme.rs create mode 100644 core/embed/rust/src/ui/model_tr/bootloader/title.rs create mode 100644 core/embed/rust/src/ui/model_tr/component/result.rs create mode 100644 core/embed/rust/src/ui/model_tr/res/trezor_empty.toif create mode 100644 core/embed/rust/src/ui/model_tr/screens.rs diff --git a/core/embed/rust/src/ui/model_tr/bootloader/confirm.rs b/core/embed/rust/src/ui/model_tr/bootloader/confirm.rs new file mode 100644 index 000000000..234fc470a --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/bootloader/confirm.rs @@ -0,0 +1,116 @@ +use crate::ui::{ + component::{text::paragraphs::Paragraphs, Child, Component, Event, EventCtx, Pad}, + constant::screen, + display, + display::Color, + geometry::{Point, Rect}, + model_tr::{ + component::{Button, ButtonMsg::Clicked}, + constant::{HEIGHT, WIDTH}, + theme::WHITE, + }, +}; + +use super::ReturnToC; + +#[derive(Copy, Clone)] +pub enum ConfirmMsg { + Cancel = 1, + Confirm = 2, +} + +impl ReturnToC for ConfirmMsg { + fn return_to_c(self) -> u32 { + self as u32 + } +} + +pub struct Confirm { + bg: Pad, + bg_color: Color, + icon: Option<&'static [u8]>, + message: Child>, + left: Child>, + right: Child>, + confirm_left: bool, +} + +impl Confirm { + pub fn new( + bg_color: Color, + icon: Option<&'static [u8]>, + message: Paragraphs<&'static str>, + left: Button<&'static str>, + right: Button<&'static str>, + confirm_left: bool, + ) -> Self { + let mut instance = Self { + bg: Pad::with_background(bg_color), + bg_color, + icon, + message: Child::new(message), + left: Child::new(left), + right: Child::new(right), + confirm_left, + }; + instance.bg.clear(); + instance + } +} + +impl Component for Confirm { + type Msg = ConfirmMsg; + + fn place(&mut self, bounds: Rect) -> Rect { + self.bg + .place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT))); + self.message + .place(Rect::new(Point::new(10, 0), Point::new(118, 50))); + + let button_area = bounds.split_bottom(12).1; + self.left.place(button_area); + self.right.place(button_area); + + bounds + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + if let Some(Clicked) = self.left.event(ctx, event) { + return if self.confirm_left { + Some(Self::Msg::Confirm) + } else { + Some(Self::Msg::Cancel) + }; + }; + if let Some(Clicked) = self.right.event(ctx, event) { + return if self.confirm_left { + Some(Self::Msg::Cancel) + } else { + Some(Self::Msg::Confirm) + }; + }; + None + } + + fn paint(&mut self) { + self.bg.paint(); + + if let Some(icon) = self.icon { + display::icon( + Point::new(screen().center().x, 45), + icon, + WHITE, + self.bg_color, + ); + } + + self.message.paint(); + self.left.paint(); + self.right.paint(); + } + + fn bounds(&self, sink: &mut dyn FnMut(Rect)) { + self.left.bounds(sink); + self.right.bounds(sink); + } +} diff --git a/core/embed/rust/src/ui/model_tr/bootloader/intro.rs b/core/embed/rust/src/ui/model_tr/bootloader/intro.rs new file mode 100644 index 000000000..86125f4f6 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/bootloader/intro.rs @@ -0,0 +1,110 @@ +use crate::ui::{ + component::{text::paragraphs::Paragraphs, Child, Component, Event, EventCtx, Pad}, + geometry::{LinearPlacement, Point, Rect}, + model_tr::{ + bootloader::{ + theme::{BLD_BG, TEXT_NORMAL}, + title::Title, + ReturnToC, + }, + component::ButtonMsg::Clicked, + }, +}; + +use crate::ui::model_tr::{ + bootloader::theme::bld_button_default, + component::{Button, ButtonPos}, + constant::{HEIGHT, WIDTH}, +}; + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum IntroMsg { + Menu = 1, + Host = 2, +} +impl ReturnToC for IntroMsg { + fn return_to_c(self) -> u32 { + self as u32 + } +} + +pub struct Intro { + bg: Pad, + title: Child, + host: Child<Button<&'static str>>, + menu: Child<Button<&'static str>>, + text: Child<Paragraphs<&'static str>>, +} + +impl Intro { + pub fn new(bld_version: &'static str, vendor: &'static str, version: &'static str) -> Self { + let p1 = Paragraphs::new() + .add(TEXT_NORMAL, version) + .add(TEXT_NORMAL, vendor) + .with_placement(LinearPlacement::vertical().align_at_start()); + + let mut instance = Self { + bg: Pad::with_background(BLD_BG), + title: Child::new(Title::new(bld_version)), + host: Child::new(Button::with_text( + ButtonPos::Left, + "INSTALL FIRMWARE", + bld_button_default(), + )), + menu: Child::new(Button::with_text( + ButtonPos::Right, + "MENU", + bld_button_default(), + )), + text: Child::new(p1), + }; + + instance.bg.clear(); + instance + } +} + +impl Component for Intro { + type Msg = IntroMsg; + + fn place(&mut self, bounds: Rect) -> Rect { + self.bg + .place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT))); + self.title + .place(Rect::new(Point::new(10, 0), Point::new(128, 8))); + + let button_area = bounds.split_bottom(12).1; + self.host.place(button_area); + self.menu.place(button_area); + + self.text + .place(Rect::new(Point::new(10, 20), Point::new(118, 50))); + bounds + } + + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { + if let Some(Clicked) = self.menu.event(ctx, event) { + return Some(Self::Msg::Menu); + }; + if let Some(Clicked) = self.host.event(ctx, event) { + return Some(Self::Msg::Host); + }; + None + } + + fn paint(&mut self) { + self.bg.paint(); + self.title.paint(); + self.text.paint(); + self.host.paint(); + self.menu.paint(); + } + + fn bounds(&self, sink: &mut dyn FnMut(Rect)) { + self.title.bounds(sink); + self.text.bounds(sink); + self.host.bounds(sink); + self.menu.bounds(sink); + } +} diff --git a/core/embed/rust/src/ui/model_tr/bootloader/menu.rs b/core/embed/rust/src/ui/model_tr/bootloader/menu.rs new file mode 100644 index 000000000..ee8f41567 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/bootloader/menu.rs @@ -0,0 +1,104 @@ +use crate::ui::{ + component::{Child, Component, Event, EventCtx, Pad}, + geometry::{Point, Rect}, + model_tr::{ + bootloader::{theme::BLD_BG, title::Title, ReturnToC}, + constant::{HEIGHT, WIDTH}, + }, +}; + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum MenuMsg { + Close = 1, + Reboot = 2, + FactoryReset = 3, +} +impl ReturnToC for MenuMsg { + fn return_to_c(self) -> u32 { + self as u32 + } +} + +pub struct Menu { + bg: Pad, + title: Child<Title>, + // close: Child<Button<&'static str>>, + // reboot: Child<Button<&'static str>>, + // reset: Child<Button<&'static str>>, +} + +impl Menu { + pub fn new(bld_version: &'static str) -> Self { + // let content_reboot = IconText::new("REBOOT", REBOOT); + // let content_reset = IconText::new("FACTORY RESET", ERASE); + + let mut instance = Self { + bg: Pad::with_background(BLD_BG), + title: Child::new(Title::new(bld_version)), + // close: Child::new( + // Button::with_icon(CLOSE) + // .styled(button_bld_menu()) + // .with_expanded_touch_area(Insets::uniform(13)), + // ), + // reboot: Child::new( + // Button::with_icon_and_text(content_reboot).styled(button_bld_menu_item()), + // ), + // reset: Child::new( + // Button::with_icon_and_text(content_reset).styled(button_bld_menu_item()), + // ), + }; + instance.bg.clear(); + instance + } +} + +impl Component for Menu { + type Msg = MenuMsg; + + fn place(&mut self, bounds: Rect) -> Rect { + self.bg + .place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT))); + self.title + .place(Rect::new(Point::new(10, 0), Point::new(128, 8))); + // self.close.place(Rect::new( + // Point::new(187, 15), + // Point::new(187 + 38, 15 + 38), + // )); + // self.reboot + // .place(Rect::new(Point::new(16, 66), Point::new(16 + 209, 66 + 48))); + // self.reset.place(Rect::new( + // Point::new(16, 122), + // Point::new(16 + 209, 122 + 48), + // )); + bounds + } + + fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> { + // if let Some(Clicked) = self.close.event(ctx, event) { + // return Some(Self::Msg::Close); + // } + // if let Some(Clicked) = self.reboot.event(ctx, event) { + // return Some(Self::Msg::Reboot); + // } + // if let Some(Clicked) = self.reset.event(ctx, event) { + // return Some(Self::Msg::FactoryReset); + // } + + None + } + + fn paint(&mut self) { + self.bg.paint(); + self.title.paint(); + // self.close.paint(); + // self.reboot.paint(); + // self.reset.paint(); + } + + fn bounds(&self, _sink: &mut dyn FnMut(Rect)) { + // self.close.bounds(sink); + // self.reboot.bounds(sink); + // self.reset.bounds(sink); + } +} diff --git a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs new file mode 100644 index 000000000..c41373d57 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs @@ -0,0 +1,393 @@ +use crate::{ + trezorhal::io::io_button_read, + ui::{ + component::{Component, Never}, + display::{self, Font}, + geometry::Point, + model_tr::constant, + }, +}; + +mod confirm; +mod intro; +mod menu; +mod theme; +mod title; + +use crate::ui::{ + component::{text::paragraphs::Paragraphs, Event, EventCtx}, + constant::{screen, BACKLIGHT_NORMAL, WIDTH}, + display::{fade_backlight_duration, Color, TextOverlay}, + event::ButtonEvent, + geometry::{LinearPlacement, Offset, Rect}, + model_tr::{ + bootloader::{ + confirm::Confirm, + intro::Intro, + menu::Menu, + theme::{bld_button_cancel, bld_button_default, BLD_BG, BLD_FG}, + }, + component::{Button, ButtonPos, ResultScreen}, + theme::LOGO_EMPTY, + }, + util::{from_c_array, from_c_str}, +}; + +const SCREEN_ADJ: Rect = screen().split_top(64).0; + +pub trait ReturnToC { + fn return_to_c(self) -> u32; +} + +impl ReturnToC for Never { + fn return_to_c(self) -> u32 { + unreachable!() + } +} + +impl ReturnToC for () { + fn return_to_c(self) -> u32 { + 0 + } +} + +fn button_eval() -> Option<ButtonEvent> { + let event = io_button_read(); + if event == 0 { + return None; + } + + let event_type = event >> 24; + let event_btn = event & 0xFFFFFF; + + let event = ButtonEvent::new(event_type, event_btn); + + if let Ok(event) = event { + return Some(event); + } + None +} + +fn run<F>(frame: &mut F) -> u32 +where + F: Component, + F::Msg: ReturnToC, +{ + frame.place(SCREEN_ADJ); + frame.paint(); + fade_backlight_duration(BACKLIGHT_NORMAL as _, 500); + + while button_eval().is_some() {} + + loop { + let event = button_eval(); + if let Some(e) = event { + let mut ctx = EventCtx::new(); + let msg = frame.event(&mut ctx, Event::Button(e)); + + if let Some(message) = msg { + return message.return_to_c(); + } + + frame.paint(); + } + } +} + +#[no_mangle] +extern "C" fn screen_install_confirm( + vendor_str: *const cty::c_char, + vendor_str_len: u8, + version: *const cty::c_char, + downgrade: bool, + vendor: bool, +) -> u32 { + let text = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) }); + let version = unwrap!(unsafe { from_c_str(version) }); + + const ICON: Option<&'static [u8]> = None; //Some(RECEIVE); + + let msg = if downgrade { + "Downgrade firmware by" + } else if vendor { + "Change vendor to" + } else { + "Update firmware by" + }; + + let mut message = Paragraphs::new() + .add(theme::TEXT_NORMAL, msg) + .centered() + .add(theme::TEXT_NORMAL, text) + .centered() + .add(theme::TEXT_NORMAL, version) + .centered(); + + if vendor || downgrade { + message = message + .add(theme::TEXT_BOLD, "Seed will be erased!") + .centered(); + } + + message = message.with_placement(LinearPlacement::vertical().align_at_center()); + + let left = Button::with_text(ButtonPos::Left, "CANCEL", bld_button_cancel()); + let right = Button::with_text(ButtonPos::Right, "INSTALL", bld_button_default()); + + let mut frame = Confirm::new(BLD_BG, ICON, message, left, right, false); + + run(&mut frame) +} + +#[no_mangle] +extern "C" fn screen_wipe_confirm() -> u32 { + const ICON: Option<&'static [u8]> = None; //Some(ERASE_BIG); + + let message = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Do you really want to wipe the device?") + .centered() + .add(theme::TEXT_BOLD, "Seed will be erased!") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let left = Button::with_text(ButtonPos::Left, "WIPE", bld_button_default()); + let right = Button::with_text(ButtonPos::Right, "CANCEL", bld_button_cancel()); + + let mut frame = Confirm::new(BLD_BG, ICON, message, left, right, true); + + run(&mut frame) +} + +#[no_mangle] +extern "C" fn screen_menu(bld_version: *const cty::c_char) -> u32 { + let bld_version = unwrap!(unsafe { from_c_str(bld_version) }); + + run(&mut Menu::new(bld_version)) +} + +#[no_mangle] +extern "C" fn screen_intro( + bld_version: *const cty::c_char, + vendor_str: *const cty::c_char, + vendor_str_len: u8, + version: *const cty::c_char, +) -> u32 { + let vendor = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) }); + let version = unwrap!(unsafe { from_c_str(version) }); + let bld_version = unwrap!(unsafe { from_c_str(bld_version) }); + + run(&mut Intro::new(bld_version, vendor, version)) +} + +fn screen_progress( + text: &str, + progress: u16, + initialize: bool, + fg_color: Color, + bg_color: Color, + _icon: Option<(&[u8], Color)>, +) -> u32 { + if initialize { + display::rect_fill(constant::screen(), bg_color); + } + + let loader_area = Rect::new(Point::new(5, 24), Point::new(WIDTH - 5, 24 + 16)); + + let mut text = TextOverlay::new(text, Font::NORMAL); + text.place(loader_area.center() + Offset::y(Font::NORMAL.text_height() / 2)); + + let fill_to = (loader_area.width() as u32 * progress as u32) / 1000; + + display::bar_with_text_and_fill(loader_area, Some(text), fg_color, bg_color, 0, fill_to as _); + + // display::text_center( + // Point::new(constant::WIDTH / 2, 100), + // text, + // Font::NORMAL, + // fg_color, + // bg_color, + // ); + // display::loader(progress, -20, fg_color, bg_color, icon); + 0 +} + +const INITIAL_INSTALL_LOADER_COLOR: Color = Color::rgb(0x4A, 0x90, 0xE2); + +#[no_mangle] +extern "C" fn screen_install_progress( + progress: u16, + initialize: bool, + _initial_setup: bool, +) -> u32 { + screen_progress( + "INSTALLING FIRMWARE", + progress, + initialize, + BLD_FG, + BLD_BG, + None, //Some((theme::RECEIVE, fg_color)), + ) +} + +#[no_mangle] +extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) -> u32 { + screen_progress( + "WIPING DEVICE", + progress, + initialize, + theme::BLD_FG, + BLD_BG, + None, //Some((theme::ERASE_BIG, theme::BLD_FG)), + ) +} + +#[no_mangle] +extern "C" fn screen_connect() -> u32 { + let mut frame = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Waiting for host...") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} + +#[no_mangle] +extern "C" fn screen_wipe_success() -> u32 { + let m_top = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Device wiped") + .centered() + .add(theme::TEXT_NORMAL, "successfully.") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let m_bottom = Paragraphs::new() + .add(theme::TEXT_NORMAL, "PLEASE RECONNECT") + .centered() + .add(theme::TEXT_NORMAL, "THE DEVICE") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(BLD_FG, BLD_BG, m_top, m_bottom, true); + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} + +#[no_mangle] +extern "C" fn screen_wipe_fail() -> u32 { + let m_top = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Device wipe was") + .centered() + .add(theme::TEXT_NORMAL, "not successful.") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let m_bottom = Paragraphs::new() + .add(theme::TEXT_NORMAL, "PLEASE RECONNECT") + .centered() + .add(theme::TEXT_NORMAL, "THE DEVICE") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(BLD_FG, BLD_BG, m_top, m_bottom, true); + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} + +#[no_mangle] +extern "C" fn screen_boot_empty(_firmware_present: bool) { + display::icon(SCREEN_ADJ.center(), LOGO_EMPTY, BLD_FG, BLD_BG); +} + +#[no_mangle] +extern "C" fn screen_install_fail() -> u32 { + let m_top = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Firmware installation was") + .centered() + .add(theme::TEXT_NORMAL, "not successful.") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let m_bottom = Paragraphs::new() + .add(theme::TEXT_NORMAL, "PLEASE RECONNECT") + .centered() + .add(theme::TEXT_NORMAL, "THE DEVICE") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(BLD_FG, BLD_BG, m_top, m_bottom, true); + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} + +fn screen_install_success_bld(msg: &'static str, complete_draw: bool) -> u32 { + let m_top = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Firmware installed") + .centered() + .add(theme::TEXT_NORMAL, "successfully.") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let m_bottom = Paragraphs::new() + .add(theme::TEXT_NORMAL, msg) + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(BLD_FG, BLD_BG, m_top, m_bottom, complete_draw); + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} + +fn screen_install_success_initial(msg: &'static str, complete_draw: bool) -> u32 { + let m_top = Paragraphs::new() + .add(theme::TEXT_NORMAL, "Firmware installed") + .centered() + .add(theme::TEXT_NORMAL, "successfully.") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let m_bottom = Paragraphs::new() + .add(theme::TEXT_NORMAL, msg) + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(BLD_FG, BLD_BG, m_top, m_bottom, complete_draw); + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} + +#[no_mangle] +extern "C" fn screen_install_success( + reboot_msg: *const cty::c_char, + initial_setup: bool, + complete_draw: bool, +) -> u32 { + let msg = unwrap!(unsafe { from_c_str(reboot_msg) }); + if initial_setup { + screen_install_success_initial(msg, complete_draw) + } else { + screen_install_success_bld(msg, complete_draw) + } +} + +#[no_mangle] +extern "C" fn screen_welcome() -> u32 { + let mut frame = Paragraphs::new() + .add(theme::TEXT_BOLD, "Get started with") + .centered() + .add(theme::TEXT_BOLD, "your trezor at") + .centered() + .add(theme::TEXT_BOLD, "trezor.io/start") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + frame.place(SCREEN_ADJ); + frame.paint(); + 0 +} diff --git a/core/embed/rust/src/ui/model_tr/bootloader/theme.rs b/core/embed/rust/src/ui/model_tr/bootloader/theme.rs new file mode 100644 index 000000000..33335ec83 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/bootloader/theme.rs @@ -0,0 +1,50 @@ +use crate::ui::{ + component::text::TextStyle, + display::{Color, Font}, + model_tr::{ + component::{ButtonStyle, ButtonStyleSheet}, + theme::{BG, BLACK, FG, WHITE}, + }, +}; + +pub const BLD_BG: Color = BLACK; +pub const BLD_FG: Color = WHITE; + +// Commonly used corner radius (i.e. for buttons). +pub const RADIUS: u8 = 2; + +// Size of icons in the UI (i.e. inside buttons). +pub const ICON_SIZE: i32 = 16; + +pub fn bld_button_default() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::NORMAL, + text_color: BG, + border_horiz: true, + }, + active: &ButtonStyle { + font: Font::NORMAL, + text_color: FG, + border_horiz: true, + }, + } +} + +pub fn bld_button_cancel() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: Font::NORMAL, + text_color: FG, + border_horiz: false, + }, + active: &ButtonStyle { + font: Font::NORMAL, + text_color: BG, + border_horiz: false, + }, + } +} + +pub const TEXT_NORMAL: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG); +pub const TEXT_BOLD: TextStyle = TextStyle::new(Font::NORMAL, BLD_FG, BLD_BG, BLD_FG, BLD_FG); diff --git a/core/embed/rust/src/ui/model_tr/bootloader/title.rs b/core/embed/rust/src/ui/model_tr/bootloader/title.rs new file mode 100644 index 000000000..64f5f0d56 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/bootloader/title.rs @@ -0,0 +1,52 @@ +use crate::ui::{ + component::{Component, Event, EventCtx, Never}, + display::{self, Font}, + geometry::{Point, Rect}, + model_tr::bootloader::theme::{BLD_BG, BLD_FG}, +}; + +pub struct Title { + version: &'static str, + area: Rect, +} + +impl Title { + pub fn new(version: &'static str) -> Self { + Self { + version, + area: Rect::zero(), + } + } +} + +impl Component for Title { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + self.area = bounds; + bounds + } + + fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> { + None + } + + fn paint(&mut self) { + display::text_top_left( + self.area.top_left(), + "BOOTLOADER", + Font::NORMAL, + BLD_FG, + BLD_BG, + ); + display::text_top_left( + Point::new(self.area.top_left().x + 65, self.area.top_left().y), + self.version, + Font::NORMAL, + BLD_FG, + BLD_BG, + ); + } + + fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {} +} diff --git a/core/embed/rust/src/ui/model_tr/component/mod.rs b/core/embed/rust/src/ui/model_tr/component/mod.rs index a54a6d08d..7222b6f18 100644 --- a/core/embed/rust/src/ui/model_tr/component/mod.rs +++ b/core/embed/rust/src/ui/model_tr/component/mod.rs @@ -15,6 +15,7 @@ mod loader; mod page; mod passphrase; mod pin; +mod result; mod result_anim; mod result_popup; mod scrollbar; @@ -43,6 +44,7 @@ pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet}; pub use page::ButtonPage; pub use passphrase::{PassphraseEntry, PassphraseEntryMsg}; pub use pin::{PinEntry, PinEntryMsg}; +pub use result::ResultScreen; pub use result_anim::{ResultAnim, ResultAnimMsg}; pub use result_popup::{ResultPopup, ResultPopupMsg}; pub use scrollbar::ScrollBar; diff --git a/core/embed/rust/src/ui/model_tr/component/result.rs b/core/embed/rust/src/ui/model_tr/component/result.rs new file mode 100644 index 000000000..eee484e3a --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/component/result.rs @@ -0,0 +1,84 @@ +use crate::ui::{ + component::{text::paragraphs::Paragraphs, Child, Component, Event, EventCtx, Never, Pad}, + constant::{HEIGHT, WIDTH}, + display::Color, + geometry::{Point, Rect}, +}; + +pub struct ResultScreen { + bg: Pad, + small_pad: Pad, + fg_color: Color, + bg_color: Color, + message_top: Child<Paragraphs<&'static str>>, + message_bottom: Child<Paragraphs<&'static str>>, +} + +impl ResultScreen { + pub fn new( + fg_color: Color, + bg_color: Color, + message_top: Paragraphs<&'static str>, + message_bottom: Paragraphs<&'static str>, + complete_draw: bool, + ) -> Self { + let mut instance = Self { + bg: Pad::with_background(bg_color), + small_pad: Pad::with_background(bg_color), + fg_color, + bg_color, + message_top: Child::new(message_top), + message_bottom: Child::new(message_bottom), + }; + + if complete_draw { + instance.bg.clear(); + } else { + instance.small_pad.clear(); + } + instance + } +} + +impl Component for ResultScreen { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + self.bg + .place(Rect::new(Point::new(0, 0), Point::new(WIDTH, HEIGHT))); + + self.message_top + .place(Rect::new(Point::new(0, 0), Point::new(WIDTH, 30))); + + let bottom_area = Rect::new(Point::new(0, 40), Point::new(WIDTH, HEIGHT)); + + self.small_pad.place(bottom_area); + self.message_bottom.place(bottom_area); + + bounds + } + + fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> { + None + } + + fn paint(&mut self) { + self.bg.paint(); + self.small_pad.paint(); + + // display::icon( + // Point::new(screen().center().x, 45), + // self.icon, + // self.fg_color, + // self.bg_color, + // ); + // display::rect_fill( + // Rect::from_top_left_and_size(Point::new(12, 149), Offset::new(216, 1)), + // self.fg_color, + // ); + self.message_top.paint(); + self.message_bottom.paint(); + } + + fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {} +} diff --git a/core/embed/rust/src/ui/model_tr/constant.rs b/core/embed/rust/src/ui/model_tr/constant.rs index a51b69cca..bcd2015af 100644 --- a/core/embed/rust/src/ui/model_tr/constant.rs +++ b/core/embed/rust/src/ui/model_tr/constant.rs @@ -9,6 +9,8 @@ pub const LOADER_OUTER: f32 = 20_f32; pub const LOADER_INNER: f32 = 14_f32; pub const LOADER_ICON_MAX_SIZE: i16 = 24; +pub const BACKLIGHT_NORMAL: i32 = 150; + pub const fn size() -> Offset { Offset::new(WIDTH, HEIGHT) } diff --git a/core/embed/rust/src/ui/model_tr/mod.rs b/core/embed/rust/src/ui/model_tr/mod.rs index c6eb39480..529781d27 100644 --- a/core/embed/rust/src/ui/model_tr/mod.rs +++ b/core/embed/rust/src/ui/model_tr/mod.rs @@ -1,6 +1,9 @@ +#[cfg(feature = "bootloader")] +pub mod bootloader; pub mod component; pub mod constant; pub mod theme; #[cfg(feature = "micropython")] pub mod layout; +pub mod screens; diff --git a/core/embed/rust/src/ui/model_tr/res/trezor_empty.toif b/core/embed/rust/src/ui/model_tr/res/trezor_empty.toif new file mode 100644 index 0000000000000000000000000000000000000000..98034d509c7ceb4981c8ae15b8e1dab3743135fe GIT binary patch literal 398 zcmV;90df9RPf15C06+kO0RRAFV1R(YlmFh&5oJIHllFQrDYV?SLJ@47#{{AjZb~2t z8th?$FceNRAw(~;!&n8j2&K6Q1=rw;53nOd8+c%>9Y8Fw{oT825T^?&s9*;qu0}B_ zY>fpn9y3BUaRP1e1+lJhFfdHufQVjT0V-t%TMJZdzzY$51T-}k!aNGpt_NW*V_*n? z+I)ePfgu~h>_8Y+0JLH{gqg*_uz?lA4B!T9rI~12T3QZJG&2nhrU6B_85<k#-@V%l z=%U?F01UmWcklk?XTSnJ&`?w$6a_FsL>12MMgnh`prX9Ux-X214i(WnMuecibEqi8 zmOdt!pu%-)cq&;mj~Oa>(+Vl=Oj-*THn^n;NjvfoMU8t|fP%NhA-s<u9tb3!6%Dup z6?Ax=xK|V^eC7-*gawjKI3ot-u)?I-pi)R6aW5;>7<GhT(>|z0K)W^Jg6o(N-h62V sVQikugk+e&4IdD-rIr!d3I>5|F$@exb5R9>!bfwD$1-4oquvNM0I^w~NdN!< literal 0 HcmV?d00001 diff --git a/core/embed/rust/src/ui/model_tr/screens.rs b/core/embed/rust/src/ui/model_tr/screens.rs new file mode 100644 index 000000000..cb154bd18 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/screens.rs @@ -0,0 +1,76 @@ +use crate::ui::{ + component::{text::paragraphs::Paragraphs, Component}, + geometry::LinearPlacement, + model_tr::{ + component::ResultScreen, + constant, + theme::{BLACK, TEXT_BOLD, TEXT_NORMAL, WHITE}, + }, + util::from_c_str, +}; + +#[no_mangle] +extern "C" fn screen_fatal_error(msg: *const cty::c_char, file: *const cty::c_char) -> u32 { + let m_top = if msg.is_null() { + Paragraphs::new() + .add(TEXT_BOLD, "FATAL ERROR!") + .centered() + // .add(theme::TEXT_WIPE_NORMAL, unwrap!(unsafe { from_c_str(expr) })) + // .centered() + .add(TEXT_NORMAL, unwrap!(unsafe { from_c_str(file) })) + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()) + } else { + let msg = unwrap!(unsafe { from_c_str(msg) }); + Paragraphs::new() + .add(TEXT_BOLD, "FATAL ERROR!") + .centered() + .add(TEXT_NORMAL, msg) + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()) + }; + + let m_bottom = Paragraphs::new() + .add(TEXT_BOLD, "PLEASE CONTACT") + .centered() + .add(TEXT_BOLD, "TREZOR SUPPORT") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(WHITE, BLACK, m_top, m_bottom, true); + frame.place(constant::screen()); + frame.paint(); + 0 +} + +#[no_mangle] +extern "C" fn screen_error_shutdown(label: *const cty::c_char, msg: *const cty::c_char) -> u32 { + let label = unwrap!(unsafe { from_c_str(label) }); + + let m_top = if msg.is_null() { + Paragraphs::new() + .add(TEXT_BOLD, label) + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()) + } else { + let msg = unwrap!(unsafe { from_c_str(msg) }); + Paragraphs::new() + .add(TEXT_BOLD, label) + .centered() + .add(TEXT_NORMAL, msg) + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()) + }; + + let m_bottom = Paragraphs::new() + .add(TEXT_BOLD, "PLEASE UNPLUG") + .centered() + .add(TEXT_BOLD, "THE DEVICE") + .centered() + .with_placement(LinearPlacement::vertical().align_at_center()); + + let mut frame = ResultScreen::new(WHITE, BLACK, m_top, m_bottom, true); + frame.place(constant::screen()); + frame.paint(); + 0 +} diff --git a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs index 21b04ea42..afcc4f4e0 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs @@ -14,7 +14,7 @@ mod connect; mod fwinfo; pub mod intro; pub mod menu; -mod theme; +pub mod theme; mod title; use crate::ui::{