feat(core): bootloader: T3T1 startup UI

[no changelog]
pull/3626/head
Martin Milata 2 months ago
parent c277dbcfcb
commit 804d97c9d8

@ -45,8 +45,8 @@ elif TREZOR_MODEL in ('T', 'DISC1', 'DISC2'):
elif TREZOR_MODEL in ('T3T1',): elif TREZOR_MODEL in ('T3T1',):
FONT_NORMAL='Font_TTSatoshi_DemiBold_21' FONT_NORMAL='Font_TTSatoshi_DemiBold_21'
FONT_DEMIBOLD='Font_TTSatoshi_DemiBold_21' FONT_DEMIBOLD='Font_TTSatoshi_DemiBold_21'
FONT_BOLD='Font_TTSatoshi_DemiBold_21' FONT_BOLD='Font_TTHoves_Bold_17'
FONT_MONO='Font_RobotoMono_Medium_21' FONT_MONO='Font_TTSatoshi_DemiBold_21'
FONT_BIG=None FONT_BIG=None
# modtrezorcrypto # modtrezorcrypto

@ -42,8 +42,8 @@ elif TREZOR_MODEL in ('T', 'DISC2'):
elif TREZOR_MODEL in ('T3T1',): elif TREZOR_MODEL in ('T3T1',):
FONT_NORMAL='Font_TTSatoshi_DemiBold_21' FONT_NORMAL='Font_TTSatoshi_DemiBold_21'
FONT_DEMIBOLD='Font_TTSatoshi_DemiBold_21' FONT_DEMIBOLD='Font_TTSatoshi_DemiBold_21'
FONT_BOLD='Font_TTSatoshi_DemiBold_21' FONT_BOLD='Font_TTHoves_Bold_17'
FONT_MONO='Font_RobotoMono_Medium_21' FONT_MONO='Font_TTSatoshi_DemiBold_21'
FONT_BIG=None FONT_BIG=None
# modtrezorcrypto # modtrezorcrypto

@ -5,7 +5,7 @@ use crate::{
ui::{ ui::{
component::{connect::Connect, Label}, component::{connect::Connect, Label},
display::{self, Color, Font, Icon}, display::{self, Color, Font, Icon},
geometry::{Point, Rect}, geometry::{Offset, Point, Rect},
layout::simplified::{run, show}, layout::simplified::{run, show},
}, },
}; };
@ -19,11 +19,11 @@ use super::{
theme::{ theme::{
bootloader::{ bootloader::{
button_bld, button_bld_menu, button_confirm, button_wipe_cancel, button_wipe_confirm, button_bld, button_bld_menu, button_confirm, button_wipe_cancel, button_wipe_confirm,
BLD_BG, BLD_FG, BLD_TITLE_COLOR, BLD_WIPE_COLOR, CHECK24, CHECK40, DOWNLOAD32, FIRE32, BLD_BG, BLD_FG, BLD_TITLE_COLOR, BLD_WIPE_COLOR, CHECK24, CHECK40, DOWNLOAD24, FIRE32,
FIRE40, RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL, FIRE40, RESULT_FW_INSTALL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL, TEXT_WIPE_BOLD,
TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24,
}, },
BACKLIGHT_NORMAL, BLACK, FG, WHITE, BACKLIGHT_NORMAL, BLACK, GREEN_LIGHT, GREY, WHITE,
}, },
ModelMercuryFeatures, ModelMercuryFeatures,
}; };
@ -41,6 +41,7 @@ pub type BootloaderString = String<128>;
const RECONNECT_MESSAGE: &str = "PLEASE RECONNECT\nTHE DEVICE"; const RECONNECT_MESSAGE: &str = "PLEASE RECONNECT\nTHE DEVICE";
const SCREEN: Rect = ModelMercuryFeatures::SCREEN; const SCREEN: Rect = ModelMercuryFeatures::SCREEN;
const PROGRESS_TEXT_ORIGIN: Point = Point::new(2, 28);
impl ModelMercuryFeatures { impl ModelMercuryFeatures {
fn screen_progress( fn screen_progress(
@ -50,47 +51,32 @@ impl ModelMercuryFeatures {
fg_color: Color, fg_color: Color,
bg_color: Color, bg_color: Color,
icon: Option<(Icon, Color)>, icon: Option<(Icon, Color)>,
center_text: Option<&str>,
) { ) {
let loader_offset: i16 = 19;
let center_text_offset: i16 = 10;
if initialize { if initialize {
ModelMercuryFeatures::fadeout(); Self::fadeout();
display::rect_fill(SCREEN, bg_color); display::rect_fill(SCREEN, bg_color);
} }
display::text_center( display::text_left(PROGRESS_TEXT_ORIGIN, text, Font::NORMAL, BLD_FG, bg_color);
Point::new(SCREEN.width() / 2, SCREEN.height() - 45), display::loader(progress, 19, fg_color, bg_color, icon);
text, if let Some(center_text) = center_text {
Font::NORMAL, display::text_center(
BLD_FG, SCREEN.center() + Offset::y(loader_offset + center_text_offset),
bg_color, center_text,
); Font::NORMAL,
display::loader(progress, -20, fg_color, bg_color, icon); GREY,
bg_color,
);
}
display::refresh(); display::refresh();
if initialize { if initialize {
ModelMercuryFeatures::fadein(); Self::fadein();
} }
} }
fn screen_install_success_bld(msg: &str, complete_draw: bool) {
let mut frame = ResultScreen::new(
&RESULT_FW_INSTALL,
Icon::new(CHECK40),
"Firmware installed\nsuccessfully".into(),
Label::centered(msg.into(), RESULT_FW_INSTALL.title_style()).vertically_centered(),
complete_draw,
);
show(&mut frame, complete_draw);
}
fn screen_install_success_initial(msg: &str, complete_draw: bool) {
let mut frame = ResultScreen::new(
&RESULT_INITIAL,
Icon::new(CHECK40),
"Firmware installed\nsuccessfully".into(),
Label::centered(msg.into(), RESULT_INITIAL.title_style()).vertically_centered(),
complete_draw,
);
show(&mut frame, complete_draw);
}
} }
impl UIFeaturesBootloader for ModelMercuryFeatures { impl UIFeaturesBootloader for ModelMercuryFeatures {
@ -112,20 +98,36 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool) { fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool) {
let mut reboot_msg = BootloaderString::new(); let mut reboot_msg = BootloaderString::new();
let bg_color = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let fg_color = if initial_setup { GREEN_LIGHT } else { BLD_FG };
if restart_seconds >= 1 { if restart_seconds >= 1 {
unwrap!(reboot_msg.push_str("RESTARTING IN "));
// in practice, restart_seconds is 5 or less so this is fine // in practice, restart_seconds is 5 or less so this is fine
let seconds_char = b'0' + restart_seconds % 10; let seconds_char = b'0' + restart_seconds % 10;
unwrap!(reboot_msg.push(seconds_char as char)); unwrap!(reboot_msg.push(seconds_char as char));
let progress = (5 - (restart_seconds as u16)).clamp(0, 5) * 200;
Self::screen_progress(
"Restarting device",
progress,
complete_draw,
fg_color,
bg_color,
None,
Some(reboot_msg.as_str()),
);
} else { } else {
unwrap!(reboot_msg.push_str(RECONNECT_MESSAGE)); Self::screen_progress(
"Firmware installed",
1000,
complete_draw,
fg_color,
bg_color,
Some((Icon::new(CHECK24), BLD_FG)),
None,
);
} }
if initial_setup {
ModelMercuryFeatures::screen_install_success_initial(reboot_msg.as_str(), complete_draw)
} else {
ModelMercuryFeatures::screen_install_success_bld(reboot_msg.as_str(), complete_draw)
}
display::refresh(); display::refresh();
} }
@ -247,16 +249,16 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
fn screen_boot_empty(fading: bool) { fn screen_boot_empty(fading: bool) {
if fading { if fading {
ModelMercuryFeatures::fadeout(); Self::fadeout();
} }
display::rect_fill(SCREEN, BLACK); display::rect_fill(SCREEN, BLACK);
let mut frame = WelcomeScreen::new(true); let mut frame = WelcomeScreen::new();
show(&mut frame, false); show(&mut frame, false);
if fading { if fading {
ModelMercuryFeatures::fadein(); Self::fadein();
} else { } else {
display::set_backlight(BACKLIGHT_NORMAL); display::set_backlight(BACKLIGHT_NORMAL);
} }
@ -264,36 +266,30 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
} }
fn screen_wipe_progress(progress: u16, initialize: bool) { fn screen_wipe_progress(progress: u16, initialize: bool) {
ModelMercuryFeatures::screen_progress( Self::screen_progress(
"Resetting Trezor", "Resetting Trezor",
progress, progress,
initialize, initialize,
BLD_FG, BLD_FG,
BLD_WIPE_COLOR, BLD_WIPE_COLOR,
Some((Icon::new(FIRE32), BLD_FG)), Some((Icon::new(FIRE32), BLD_FG)),
None,
) )
} }
fn screen_install_progress(progress: u16, initialize: bool, initial_setup: bool) { fn screen_install_progress(progress: u16, initialize: bool, initial_setup: bool) {
let bg_color = if initial_setup { WELCOME_COLOR } else { BLD_BG }; let bg_color = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let fg_color = if initial_setup { let fg_color = if initial_setup { GREEN_LIGHT } else { BLD_FG };
Color::rgb(0x0B, 0xA5, 0x67) let icon_color = BLD_FG;
} else {
BLD_FG
};
let icon_color = if initial_setup {
Color::rgb(0x8B, 0x8B, 0x93)
} else {
BLD_FG
};
ModelMercuryFeatures::screen_progress( Self::screen_progress(
"Installing firmware", "Installing firmware",
progress, progress,
initialize, initialize,
fg_color, fg_color,
bg_color, bg_color,
Some((Icon::new(DOWNLOAD32), icon_color)), Some((Icon::new(DOWNLOAD24), icon_color)),
None,
) )
} }

@ -1,14 +1,14 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad}, component::{Component, Event, EventCtx, Never, Pad},
constant::screen, constant::screen,
display::{self, Font, Icon}, display::{self, Font},
geometry::{Alignment2D, Offset, Rect}, geometry::{Offset, Point, Rect},
}; };
use super::super::theme::{ use super::super::theme::{BLACK, GREY, WHITE};
bootloader::{START_URL, WELCOME_COLOR},
BLACK, GREY_MEDIUM, WHITE, const TEXT_ORIGIN: Point = Point::new(0, 105);
}; const STRIDE: i16 = 22;
pub struct Welcome { pub struct Welcome {
bg: Pad, bg: Pad,
@ -17,7 +17,7 @@ pub struct Welcome {
impl Welcome { impl Welcome {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
bg: Pad::with_background(WELCOME_COLOR).with_clear(), bg: Pad::with_background(BLACK).with_clear(),
} }
} }
} }
@ -35,24 +35,28 @@ impl Component for Welcome {
} }
fn paint(&mut self) { fn paint(&mut self) {
let at_width = Font::NORMAL.text_width("at ");
self.bg.paint(); self.bg.paint();
display::text_center( display::text_left(TEXT_ORIGIN, "Get started", Font::NORMAL, GREY, BLACK);
screen().top_center() + Offset::y(102), display::text_left(
"Get started with", TEXT_ORIGIN + Offset::y(STRIDE),
"with your Trezor",
Font::NORMAL, Font::NORMAL,
GREY_MEDIUM, GREY,
BLACK, BLACK,
); );
display::text_center( display::text_left(
screen().top_center() + Offset::y(126), TEXT_ORIGIN + Offset::y(2 * STRIDE),
"your Trezor at", "at",
Font::NORMAL, Font::NORMAL,
GREY_MEDIUM, GREY,
BLACK, BLACK,
); );
Icon::new(START_URL).draw( display::text_left(
screen().top_center() + Offset::y(135), TEXT_ORIGIN + Offset::new(at_width, 2 * STRIDE),
Alignment2D::TOP_CENTER, "trezor.io/start",
Font::NORMAL,
WHITE, WHITE,
BLACK, BLACK,
); );

@ -1,31 +1,25 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never}, component::{Component, Event, EventCtx, Never},
display,
geometry::{Alignment2D, Offset, Rect}, geometry::{Alignment2D, Offset, Rect},
}; };
use super::theme; use super::theme;
#[cfg(feature = "bootloader")] const TEXT_BOTTOM_MARGIN: i16 = 54;
use crate::ui::{display::Icon, model_mercury::theme::bootloader::DEVICE_NAME};
const TEXT_BOTTOM_MARGIN: i16 = 24; // matching the homescreen label margin
const ICON_TOP_MARGIN: i16 = 48; const ICON_TOP_MARGIN: i16 = 48;
#[cfg(not(feature = "bootloader"))] #[cfg(not(feature = "bootloader"))]
const MODEL_NAME_FONT: display::Font = display::Font::DEMIBOLD; const MODEL_NAME_FONT: display::Font = display::Font::DEMIBOLD;
#[cfg(not(feature = "bootloader"))] #[cfg(not(feature = "bootloader"))]
use crate::{trezorhal::model, ui::display}; use crate::trezorhal::model;
pub struct WelcomeScreen { pub struct WelcomeScreen {
area: Rect, area: Rect,
empty_lock: bool,
} }
impl WelcomeScreen { impl WelcomeScreen {
pub fn new(empty_lock: bool) -> Self { pub fn new() -> Self {
Self { Self { area: Rect::zero() }
area: Rect::zero(),
empty_lock,
}
} }
} }
@ -42,29 +36,16 @@ impl Component for WelcomeScreen {
} }
fn paint(&mut self) { fn paint(&mut self) {
let logo = if self.empty_lock { theme::ICON_LOGO.draw(
theme::ICON_LOGO_EMPTY
} else {
theme::ICON_LOGO
};
logo.draw(
self.area.top_center() + Offset::y(ICON_TOP_MARGIN), self.area.top_center() + Offset::y(ICON_TOP_MARGIN),
Alignment2D::TOP_CENTER, Alignment2D::TOP_CENTER,
theme::FG, theme::FG,
theme::BG, theme::BG,
); );
#[cfg(not(feature = "bootloader"))]
display::text_center( display::text_center(
self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN), self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN),
model::FULL_NAME, "Trezor Safe 5",
MODEL_NAME_FONT, display::Font::NORMAL,
theme::FG,
theme::BG,
);
#[cfg(feature = "bootloader")]
Icon::new(DEVICE_NAME).draw(
self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN),
Alignment2D::BOTTOM_CENTER,
theme::FG, theme::FG,
theme::BG, theme::BG,
); );

@ -12,7 +12,7 @@ pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
} }
pub fn screen_boot_full() { pub fn screen_boot_full() {
let mut frame = WelcomeScreen::new(false); let mut frame = WelcomeScreen::new();
frame.place(screen()); frame.place(screen());
display::sync(); display::sync();
frame.paint(); frame.paint();

@ -64,14 +64,11 @@ pub const FIRE40: &[u8] = include_res!("model_mercury/res/fire40.toif");
pub const REFRESH24: &[u8] = include_res!("model_mercury/res/refresh24.toif"); pub const REFRESH24: &[u8] = include_res!("model_mercury/res/refresh24.toif");
pub const MENU32: &[u8] = include_res!("model_mercury/res/menu32.toif"); pub const MENU32: &[u8] = include_res!("model_mercury/res/menu32.toif");
pub const INFO32: &[u8] = include_res!("model_mercury/res/info32.toif"); pub const INFO32: &[u8] = include_res!("model_mercury/res/info32.toif");
pub const DOWNLOAD32: &[u8] = include_res!("model_mercury/res/download32.toif"); pub const DOWNLOAD24: &[u8] = include_res!("model_mercury/res/download24.toif");
pub const WARNING40: &[u8] = include_res!("model_mercury/res/warning40.toif"); pub const WARNING40: &[u8] = include_res!("model_mercury/res/warning40.toif");
pub const CHECK24: &[u8] = include_res!("model_mercury/res/check24.toif"); pub const CHECK24: &[u8] = include_res!("model_mercury/res/check24.toif");
pub const CHECK40: &[u8] = include_res!("model_mercury/res/check40.toif"); pub const CHECK40: &[u8] = include_res!("model_mercury/res/check40.toif");
pub const DEVICE_NAME: &[u8] = include_res!("model_mercury/res/device_name_T.toif");
pub const START_URL: &[u8] = include_res!("model_mercury/res/start.toif");
pub fn button_confirm() -> ButtonStyleSheet { pub fn button_confirm() -> ButtonStyleSheet {
ButtonStyleSheet { ButtonStyleSheet {
normal: &ButtonStyle { normal: &ButtonStyle {

@ -17,19 +17,16 @@ pub const WHITE: Color = Color::rgb(0xFF, 0xFF, 0xFF);
pub const BLACK: Color = Color::rgb(0, 0, 0); pub const BLACK: Color = Color::rgb(0, 0, 0);
pub const FG: Color = WHITE; // Default foreground (text & icon) color. pub const FG: Color = WHITE; // Default foreground (text & icon) color.
pub const BG: Color = BLACK; // Default background color. pub const BG: Color = BLACK; // Default background color.
pub const RED: Color = Color::rgb(0xE7, 0x0E, 0x0E); // button pub const GREY_EXTRA_DARK: Color = Color::rgb(0x16, 0x1F, 0x24);
pub const RED_DARK: Color = Color::rgb(0xAE, 0x09, 0x09); // button pressed pub const GREY_DARK: Color = Color::rgb(0x46, 0x48, 0x4A);
pub const YELLOW: Color = Color::rgb(0xD9, 0x9E, 0x00); // button pub const GREY: Color = Color::rgb(0x8B, 0x8F, 0x93); // secondary text, subtitle, instructions
pub const YELLOW_DARK: Color = Color::rgb(0x7A, 0x58, 0x00); // button pressed pub const GREY_LIGHT: Color = Color::rgb(0xC7, 0xCD, 0xD3); // content
pub const GREEN: Color = Color::rgb(0x00, 0xAA, 0x35); // button pub const GREY_EXTRA_LIGHT: Color = Color::rgb(0xF0, 0xF0, 0xF0); // primary text, header
pub const GREEN_DARK: Color = Color::rgb(0x00, 0x55, 0x1D); // button pressed pub const GREEN: Color = Color::rgb(0x08, 0x74, 0x48);
pub const BLUE: Color = Color::rgb(0x06, 0x1E, 0xAD); // button pub const GREEN_LIGHT: Color = Color::rgb(0x0B, 0xA5, 0x67);
pub const BLUE_DARK: Color = Color::rgb(0x04, 0x10, 0x58); // button pressed pub const GREEN_LIME: Color = Color::rgb(0x9B, 0xE8, 0x87);
pub const OFF_WHITE: Color = Color::rgb(0xDE, 0xDE, 0xDE); // very light grey pub const ORANGE_DIMMED: Color = Color::rgb(0x9E, 0x57, 0x42);
pub const GREY_LIGHT: Color = Color::rgb(0x90, 0x90, 0x90); // secondary text pub const ORANGE_LIGHT: Color = Color::rgb(0xFF, 0x8D, 0x6A); // cancel button
pub const GREY_MEDIUM: Color = Color::rgb(0x4F, 0x4F, 0x4F); // button pressed
pub const GREY_DARK: Color = Color::rgb(0x35, 0x35, 0x35); // button
pub const VIOLET: Color = Color::rgb(0x95, 0x00, 0xCA);
pub const FATAL_ERROR_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E); pub const FATAL_ERROR_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41); pub const FATAL_ERROR_HIGHLIGHT_COLOR: Color = Color::rgb(0xFF, 0x41, 0x41);
@ -59,16 +56,15 @@ include_icon!(ICON_PAGE_NEXT, "model_mercury/res/page-next.toif");
include_icon!(ICON_PAGE_PREV, "model_mercury/res/page-prev.toif"); include_icon!(ICON_PAGE_PREV, "model_mercury/res/page-prev.toif");
// Large, three-color icons. // Large, three-color icons.
pub const WARN_COLOR: Color = YELLOW; pub const WARN_COLOR: Color = ORANGE_LIGHT;
pub const INFO_COLOR: Color = BLUE; pub const INFO_COLOR: Color = GREY_LIGHT;
pub const SUCCESS_COLOR: Color = GREEN; pub const SUCCESS_COLOR: Color = GREEN;
pub const ERROR_COLOR: Color = RED; pub const ERROR_COLOR: Color = ORANGE_DIMMED;
include_icon!(IMAGE_FG_SUCCESS, "model_mercury/res/fg-check48.toif"); include_icon!(IMAGE_FG_SUCCESS, "model_mercury/res/fg-check48.toif");
include_icon!(IMAGE_BG_CIRCLE, "model_mercury/res/circle48.toif"); include_icon!(IMAGE_BG_CIRCLE, "model_mercury/res/circle48.toif");
// Welcome screen. // Welcome screen.
include_icon!(ICON_LOGO, "model_mercury/res/lock_full.toif"); include_icon!(ICON_LOGO, "model_mercury/res/lock_full.toif");
include_icon!(ICON_LOGO_EMPTY, "model_mercury/res/lock_empty.toif");
pub const fn button_default() -> ButtonStyleSheet { pub const fn button_default() -> ButtonStyleSheet {
ButtonStyleSheet { ButtonStyleSheet {
@ -84,7 +80,7 @@ pub const fn button_default() -> ButtonStyleSheet {
active: &ButtonStyle { active: &ButtonStyle {
font: Font::BOLD, font: Font::BOLD,
text_color: FG, text_color: FG,
button_color: GREY_MEDIUM, button_color: GREY,
background_color: BG, background_color: BG,
border_color: FG, border_color: FG,
border_radius: RADIUS, border_radius: RADIUS,
@ -118,7 +114,7 @@ pub const fn button_moreinfo() -> ButtonStyleSheet {
text_color: FG, text_color: FG,
button_color: BG, button_color: BG,
background_color: BG, background_color: BG,
border_color: GREY_MEDIUM, border_color: GREY,
border_radius: RADIUS, border_radius: RADIUS,
border_width: 2, border_width: 2,
}, },

Loading…
Cancel
Save