mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-08 22:40:59 +00:00
feat(core/rust): model R bootloader implementation in rust
This commit is contained in:
parent
659c939080
commit
d633fa885b
118
core/embed/rust/src/ui/model_tr/bootloader/confirm.rs
Normal file
118
core/embed/rust/src/ui/model_tr/bootloader/confirm.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use crate::ui::{
|
||||
component::{
|
||||
text::paragraphs::{ParagraphVecShort, Paragraphs},
|
||||
Child, Component, Event, EventCtx, Pad,
|
||||
},
|
||||
constant::screen,
|
||||
display::{Color, Icon},
|
||||
geometry::{Point, Rect, CENTER},
|
||||
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<Icon>,
|
||||
message: Child<Paragraphs<ParagraphVecShort<&'static str>>>,
|
||||
left: Child<Button<&'static str>>,
|
||||
right: Child<Button<&'static str>>,
|
||||
confirm_left: bool,
|
||||
}
|
||||
|
||||
impl Confirm {
|
||||
pub fn new(
|
||||
bg_color: Color,
|
||||
icon: Option<Icon>,
|
||||
message: Paragraphs<ParagraphVecShort<&'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<Self::Msg> {
|
||||
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 {
|
||||
icon.draw(
|
||||
Point::new(screen().center().x, 45),
|
||||
CENTER,
|
||||
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);
|
||||
}
|
||||
}
|
116
core/embed/rust/src/ui/model_tr/bootloader/intro.rs
Normal file
116
core/embed/rust/src/ui/model_tr/bootloader/intro.rs
Normal file
@ -0,0 +1,116 @@
|
||||
use crate::ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
|
||||
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<Title>,
|
||||
host: Child<Button<&'static str>>,
|
||||
menu: Child<Button<&'static str>>,
|
||||
text: Child<Paragraphs<ParagraphVecShort<&'static str>>>,
|
||||
}
|
||||
|
||||
impl Intro {
|
||||
pub fn new(bld_version: &'static str, vendor: &'static str, version: &'static str) -> Self {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_NORMAL, version));
|
||||
messages.add(Paragraph::new(&TEXT_NORMAL, vendor));
|
||||
|
||||
let p1 =
|
||||
Paragraphs::new(messages).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);
|
||||
}
|
||||
}
|
58
core/embed/rust/src/ui/model_tr/bootloader/menu.rs
Normal file
58
core/embed/rust/src/ui/model_tr/bootloader/menu.rs
Normal file
@ -0,0 +1,58 @@
|
||||
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>,
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub fn new(bld_version: &'static str) -> Self {
|
||||
let mut instance = Self {
|
||||
bg: Pad::with_background(BLD_BG),
|
||||
title: Child::new(Title::new(bld_version)),
|
||||
};
|
||||
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)));
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
self.bg.paint();
|
||||
self.title.paint();
|
||||
}
|
||||
}
|
409
core/embed/rust/src/ui/model_tr/bootloader/mod.rs
Normal file
409
core/embed/rust/src/ui/model_tr/bootloader/mod.rs
Normal file
@ -0,0 +1,409 @@
|
||||
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::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
|
||||
Event, EventCtx,
|
||||
},
|
||||
constant::{screen, BACKLIGHT_NORMAL, WIDTH},
|
||||
display::{fade_backlight_duration, Color, Icon, TextOverlay},
|
||||
event::ButtonEvent,
|
||||
geometry::{LinearPlacement, Offset, Rect, CENTER},
|
||||
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::{ICON_FAIL, ICON_SUCCESS, 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show<F>(frame: &mut F)
|
||||
where
|
||||
F: Component,
|
||||
{
|
||||
frame.place(SCREEN_ADJ);
|
||||
display::sync();
|
||||
frame.paint();
|
||||
display::refresh();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_install_confirm(
|
||||
vendor_str: *const cty::c_char,
|
||||
vendor_str_len: u8,
|
||||
version: *const cty::c_char,
|
||||
_fingerprint: *const cty::uint8_t,
|
||||
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) });
|
||||
|
||||
let icon: Option<Icon> = None;
|
||||
|
||||
let msg = if downgrade {
|
||||
"Downgrade firmware by"
|
||||
} else if vendor {
|
||||
"Change vendor to"
|
||||
} else {
|
||||
"Update firmware by"
|
||||
};
|
||||
|
||||
let mut message = ParagraphVecShort::new();
|
||||
|
||||
message.add(Paragraph::new(&theme::TEXT_NORMAL, msg).centered());
|
||||
message.add(Paragraph::new(&theme::TEXT_NORMAL, text).centered());
|
||||
message.add(Paragraph::new(&theme::TEXT_NORMAL, version).centered());
|
||||
|
||||
if vendor || downgrade {
|
||||
message.add(Paragraph::new(&theme::TEXT_BOLD, "Seed will be erased!").centered());
|
||||
}
|
||||
|
||||
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,
|
||||
Paragraphs::new(message).with_placement(LinearPlacement::vertical().align_at_center()),
|
||||
left,
|
||||
right,
|
||||
false,
|
||||
);
|
||||
|
||||
run(&mut frame)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_wipe_confirm() -> u32 {
|
||||
let icon: Option<Icon> = None; //Some(ERASE_BIG);
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(
|
||||
Paragraph::new(
|
||||
&theme::TEXT_NORMAL,
|
||||
"Do you really want to wipe the device?",
|
||||
)
|
||||
.centered(),
|
||||
);
|
||||
messages.add(Paragraph::new(&theme::TEXT_BOLD, "Seed will be erased!").centered());
|
||||
|
||||
let message =
|
||||
Paragraphs::new(messages).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)>,
|
||||
) {
|
||||
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::refresh();
|
||||
}
|
||||
|
||||
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) {
|
||||
screen_progress(
|
||||
"INSTALLING FIRMWARE",
|
||||
progress,
|
||||
initialize,
|
||||
BLD_FG,
|
||||
BLD_BG,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) {
|
||||
screen_progress(
|
||||
"WIPING DEVICE",
|
||||
progress,
|
||||
initialize,
|
||||
theme::BLD_FG,
|
||||
BLD_BG,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_connect() {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Waiting for host...").centered());
|
||||
|
||||
let mut frame =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
show(&mut frame);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_wipe_success() {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Device wiped").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "successfully.").centered());
|
||||
|
||||
let m_top =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "PLEASE RECONNECT").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "THE DEVICE").centered());
|
||||
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(
|
||||
BLD_FG,
|
||||
BLD_BG,
|
||||
Icon::new(ICON_SUCCESS),
|
||||
m_top,
|
||||
m_bottom,
|
||||
true,
|
||||
);
|
||||
show(&mut frame);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_wipe_fail() {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Device wipe was").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "not successful.").centered());
|
||||
|
||||
let m_top =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "PLEASE RECONNECT").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "THE DEVICE").centered());
|
||||
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, Icon::new(ICON_FAIL), m_top, m_bottom, true);
|
||||
show(&mut frame);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_boot_empty(_firmware_present: bool, _fading: bool) {
|
||||
Icon::new(LOGO_EMPTY).draw(SCREEN_ADJ.center(), CENTER, BLD_FG, BLD_BG);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_install_fail() {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Firmware installation was").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "not successful.").centered());
|
||||
let m_top =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "PLEASE RECONNECT").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "THE DEVICE").centered());
|
||||
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, Icon::new(ICON_FAIL), m_top, m_bottom, true);
|
||||
show(&mut frame);
|
||||
}
|
||||
|
||||
fn screen_install_success_bld(msg: &'static str, complete_draw: bool) {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Firmware installed").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "successfully.").centered());
|
||||
|
||||
let m_top =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, msg).centered());
|
||||
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(
|
||||
BLD_FG,
|
||||
BLD_BG,
|
||||
Icon::new(ICON_SUCCESS),
|
||||
m_top,
|
||||
m_bottom,
|
||||
complete_draw,
|
||||
);
|
||||
show(&mut frame);
|
||||
}
|
||||
|
||||
fn screen_install_success_initial(msg: &'static str, complete_draw: bool) {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "Firmware installed").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, "successfully.").centered());
|
||||
|
||||
let m_top =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_NORMAL, msg).centered());
|
||||
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(
|
||||
BLD_FG,
|
||||
BLD_BG,
|
||||
Icon::new(ICON_SUCCESS),
|
||||
m_top,
|
||||
m_bottom,
|
||||
complete_draw,
|
||||
);
|
||||
show(&mut frame);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn screen_install_success(
|
||||
reboot_msg: *const cty::c_char,
|
||||
initial_setup: bool,
|
||||
complete_draw: bool,
|
||||
) {
|
||||
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() {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
messages.add(Paragraph::new(&theme::TEXT_BOLD, "Get started with").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_BOLD, "your trezor at").centered());
|
||||
messages.add(Paragraph::new(&theme::TEXT_BOLD, "trezor.io/start").centered());
|
||||
let mut frame =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
show(&mut frame);
|
||||
}
|
50
core/embed/rust/src/ui/model_tr/bootloader/theme.rs
Normal file
50
core/embed/rust/src/ui/model_tr/bootloader/theme.rs
Normal file
@ -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);
|
52
core/embed/rust/src/ui/model_tr/bootloader/title.rs
Normal file
52
core/embed/rust/src/ui/model_tr/bootloader/title.rs
Normal file
@ -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)) {}
|
||||
}
|
@ -4,6 +4,7 @@ mod dialog;
|
||||
mod frame;
|
||||
mod loader;
|
||||
mod page;
|
||||
mod result;
|
||||
mod result_anim;
|
||||
mod result_popup;
|
||||
|
||||
@ -15,5 +16,6 @@ pub use dialog::{Dialog, DialogMsg};
|
||||
pub use frame::Frame;
|
||||
pub use loader::{Loader, LoaderMsg, LoaderStyle, LoaderStyleSheet};
|
||||
pub use page::ButtonPage;
|
||||
pub use result::ResultScreen;
|
||||
pub use result_anim::{ResultAnim, ResultAnimMsg};
|
||||
pub use result_popup::{ResultPopup, ResultPopupMsg};
|
||||
|
85
core/embed/rust/src/ui/model_tr/component/result.rs
Normal file
85
core/embed/rust/src/ui/model_tr/component/result.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use crate::ui::{
|
||||
component::{
|
||||
text::paragraphs::{ParagraphStrType, ParagraphVecShort, Paragraphs},
|
||||
Child, Component, Event, EventCtx, Never, Pad,
|
||||
},
|
||||
constant::{screen, HEIGHT, WIDTH},
|
||||
display::{Color, Icon},
|
||||
geometry::{Offset, Point, Rect, CENTER},
|
||||
};
|
||||
|
||||
pub struct ResultScreen<T> {
|
||||
bg: Pad,
|
||||
small_pad: Pad,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
icon: Icon,
|
||||
message_top: Child<Paragraphs<ParagraphVecShort<T>>>,
|
||||
message_bottom: Child<Paragraphs<ParagraphVecShort<T>>>,
|
||||
}
|
||||
|
||||
impl<T: ParagraphStrType> ResultScreen<T> {
|
||||
pub fn new(
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
icon: Icon,
|
||||
message_top: Paragraphs<ParagraphVecShort<T>>,
|
||||
message_bottom: Paragraphs<ParagraphVecShort<T>>,
|
||||
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,
|
||||
icon,
|
||||
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<T: ParagraphStrType> Component for ResultScreen<T> {
|
||||
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, 26), Point::new(WIDTH, 40)));
|
||||
|
||||
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();
|
||||
|
||||
self.icon.draw(
|
||||
screen().top_center() + Offset::y(12),
|
||||
CENTER,
|
||||
self.fg_color,
|
||||
self.bg_color,
|
||||
);
|
||||
|
||||
self.message_top.paint();
|
||||
self.message_bottom.paint();
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ pub const LOADER_OUTER: f32 = 32_f32;
|
||||
pub const LOADER_INNER: f32 = 18_f32;
|
||||
pub const LOADER_ICON_MAX_SIZE: i16 = 8;
|
||||
|
||||
pub const BACKLIGHT_NORMAL: i32 = 150;
|
||||
|
||||
pub const fn size() -> Offset {
|
||||
Offset::new(WIDTH, HEIGHT)
|
||||
}
|
||||
|
@ -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;
|
||||
|
BIN
core/embed/rust/src/ui/model_tr/res/trezor_empty.toif
Normal file
BIN
core/embed/rust/src/ui/model_tr/res/trezor_empty.toif
Normal file
Binary file not shown.
93
core/embed/rust/src/ui/model_tr/screens.rs
Normal file
93
core/embed/rust/src/ui/model_tr/screens.rs
Normal file
@ -0,0 +1,93 @@
|
||||
#[cfg(feature = "micropython")]
|
||||
use crate::micropython::buffer::StrBuffer;
|
||||
use crate::ui::{
|
||||
component::{
|
||||
text::paragraphs::{Paragraph, ParagraphVecShort, Paragraphs, VecExt},
|
||||
Component,
|
||||
},
|
||||
display::Icon,
|
||||
geometry::LinearPlacement,
|
||||
model_tr::{
|
||||
component::ResultScreen,
|
||||
constant,
|
||||
theme::{BLACK, ICON_FAIL, TEXT_BOLD, TEXT_NORMAL, WHITE},
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "micropython"))]
|
||||
// SAFETY: Actually safe but see below
|
||||
unsafe fn get_str(text: &str) -> &str {
|
||||
text
|
||||
}
|
||||
#[cfg(feature = "micropython")]
|
||||
// SAFETY: The caller is responsible for ensuring that the StrBuffer does not
|
||||
// escape the lifetime of the original &str.
|
||||
unsafe fn get_str(text: &str) -> StrBuffer {
|
||||
unsafe { StrBuffer::from_ptr_and_len(text.as_ptr(), text.len()) }
|
||||
}
|
||||
|
||||
pub fn screen_fatal_error(msg: Option<&str>, file: &str) {
|
||||
// SAFETY: these will get placed into `frame` which does not outlive this
|
||||
// function
|
||||
let msg = msg.map(|s| unsafe { get_str(s) });
|
||||
let file = unsafe { get_str(file) };
|
||||
|
||||
let m_top = if let Some(msg) = msg {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, "FATAL ERROR!".into()).centered());
|
||||
messages.add(Paragraph::new(&TEXT_NORMAL, msg).centered());
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center())
|
||||
} else {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, "FATAL ERROR!".into()).centered());
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_NORMAL, file).centered());
|
||||
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center())
|
||||
};
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, "PLEASE CONTACT".into()).centered());
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, "TREZOR SUPPORT".into()).centered());
|
||||
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(WHITE, BLACK, Icon::new(ICON_FAIL), m_top, m_bottom, true);
|
||||
frame.place(constant::screen());
|
||||
frame.paint();
|
||||
}
|
||||
|
||||
pub fn screen_error_shutdown(msg: Option<&str>, label: &str) {
|
||||
// SAFETY: these will get placed into `frame` which does not outlive this
|
||||
// function
|
||||
let msg = msg.map(|s| unsafe { get_str(s) });
|
||||
let label = unsafe { get_str(label) };
|
||||
|
||||
let m_top = if let Some(msg) = msg {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, label).centered());
|
||||
messages.add(Paragraph::new(&TEXT_NORMAL, msg).centered());
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center())
|
||||
} else {
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, label).centered());
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center())
|
||||
};
|
||||
|
||||
let mut messages = ParagraphVecShort::new();
|
||||
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, "PLEASE UNPLUG".into()).centered());
|
||||
messages.add(Paragraph::new(&TEXT_BOLD, "THE DEVICE".into()).centered());
|
||||
let m_bottom =
|
||||
Paragraphs::new(messages).with_placement(LinearPlacement::vertical().align_at_center());
|
||||
|
||||
let mut frame = ResultScreen::new(WHITE, BLACK, Icon::new(ICON_FAIL), m_top, m_bottom, true);
|
||||
frame.place(constant::screen());
|
||||
frame.paint();
|
||||
}
|
@ -16,6 +16,9 @@ pub const BG: Color = BLACK; // Default background color.
|
||||
pub const ICON_SUCCESS: &[u8] = include_res!("model_tr/res/success.toif");
|
||||
pub const ICON_FAIL: &[u8] = include_res!("model_tr/res/fail.toif");
|
||||
|
||||
// BLD icons
|
||||
pub const LOGO_EMPTY: &[u8] = include_res!("model_tr/res/trezor_empty.toif");
|
||||
|
||||
pub fn button_default() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
|
BIN
core/embed/rust/src/ui/model_tt/res/trezor_empty_white.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/trezor_empty_white.toif
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user