From cf1e892d13f3f0a2ec676392f2ee472bc751c2eb Mon Sep 17 00:00:00 2001 From: obrusvit Date: Wed, 9 Jul 2025 21:47:12 +0200 Subject: [PATCH] feat(eckhart): bld welcome screen [no changelog] --- .../bootloader/welcome_screen.rs | 105 +++++++++++------- .../src/ui/layout_eckhart/theme/bootloader.rs | 32 +++++- .../src/ui/layout_eckhart/ui_bootloader.rs | 2 - 3 files changed, 93 insertions(+), 46 deletions(-) diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/welcome_screen.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/welcome_screen.rs index f5bc53af47..7d8afc124f 100644 --- a/core/embed/rust/src/ui/layout_eckhart/bootloader/welcome_screen.rs +++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/welcome_screen.rs @@ -1,23 +1,23 @@ use crate::{ strutil::TString, + trezorhal::ble, ui::{ component::{Component, Event, EventCtx}, - geometry::{Offset, Point, Rect}, + geometry::{Alignment2D, Offset, Point, Rect}, shape::{self, Renderer}, }, }; use super::{ - super::{constant::SCREEN, fonts, theme}, - BldActionBar, BldHeader, + super::{component::Button, constant::SCREEN, fonts, theme}, + BldActionBar, BldActionBarMsg, BldHeader, }; #[cfg(feature = "power_manager")] use super::BldHeaderMsg; -// TODO: adjust the origin -const TEXT_ORIGIN: Point = Point::new(24, 205); -const STRIDE: i16 = 38; +const TEXT_ORIGIN: Point = Point::new(24, 76); +const STRIDE: i16 = 46; #[repr(u32)] #[derive(Copy, Clone, ToPrimitive)] @@ -27,48 +27,60 @@ pub enum WelcomeMsg { Menu = 3, } -/// Bootloader welcome screen +/// Full-screen component for Bootloader welcome screen. This is the first +/// screen shown after the bootloader is started on empty device. The user can +/// initiate pairing. If the device is already paired, the screen shows a +/// message that Trezor is paired and Bootloader Menu is accessible. pub struct BldWelcomeScreen { header: Option>, - action_bar: Option, + action_bar: BldActionBar, + ble_paired: bool, } impl BldWelcomeScreen { pub fn new() -> Self { + let ble_paired = ble::peer_count() > 0; + let (header, button) = if ble_paired { + ( + Some(BldHeader::new(TString::empty()).with_menu_button()), + Button::with_text("More at trezor.io/start".into()) + .styled(theme::bootloader::button_welcome_screen()) + .initially_enabled(false), + ) + } else { + ( + None, + Button::with_text("Tap to begin setup".into()) + .styled(theme::bootloader::button_welcome_screen()) + .initially_enabled(true), + ) + }; Self { - header: Some(BldHeader::new(TString::empty()).with_menu_button()), - action_bar: None, + header, + action_bar: BldActionBar::new_single(button), + ble_paired, } } - - pub fn with_header(mut self, header: BldHeader<'static>) -> Self { - self.header = Some(header); - self - } - - pub fn with_action_bar(mut self, action_bar: BldActionBar) -> Self { - self.action_bar = Some(action_bar); - self - } } impl Component for BldWelcomeScreen { type Msg = WelcomeMsg; - fn place(&mut self, bounds: Rect) -> Rect { + fn place(&mut self, _bounds: Rect) -> Rect { let (header_area, rest) = SCREEN.split_top(theme::HEADER_HEIGHT); let (_rest, action_bar_area) = rest.split_bottom(theme::ACTION_BAR_HEIGHT); self.header.place(header_area); self.action_bar.place(action_bar_area); - bounds + SCREEN } fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option { - #[cfg(all(feature = "ble", feature = "button"))] - if let Event::Button(_) = _event { + #[cfg(feature = "ble")] + if let Some(BldActionBarMsg::Confirmed) = self.action_bar.event(_ctx, _event) { return Some(WelcomeMsg::PairingMode); } + #[cfg(feature = "power_manager")] if let Some(BldHeaderMsg::Menu) = self.header.event(_ctx, _event) { return Some(WelcomeMsg::Menu); @@ -77,31 +89,40 @@ impl Component for BldWelcomeScreen { } fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { - self.action_bar.render(target); #[cfg(feature = "power_manager")] self.header.render(target); let font = fonts::FONT_SATOSHI_REGULAR_38; - shape::Text::new(TEXT_ORIGIN, "Get started", font) - .with_fg(theme::GREY) + shape::Text::new(TEXT_ORIGIN, "Trezor", font) + .with_fg(theme::GREY_LIGHT) .render(target); - - shape::Text::new(TEXT_ORIGIN + Offset::y(STRIDE), "with your Trezor", font) - .with_fg(theme::GREY) + shape::Text::new(TEXT_ORIGIN + Offset::y(STRIDE), "Safe", font) + .with_fg(theme::GREY_LIGHT) .render(target); - - shape::Text::new(TEXT_ORIGIN + Offset::y(2 * STRIDE), "at", font) - .with_fg(theme::GREY) - .render(target); - - let at_width = font.text_width("at "); - - shape::Text::new( - TEXT_ORIGIN + Offset::new(at_width, 2 * STRIDE), - "trezor.io/start", - font, + shape::ToifImage::new( + TEXT_ORIGIN + Offset::y(2 * STRIDE), + theme::bootloader::ICON_SEVEN.toif, ) - .with_fg(theme::GREY_EXTRA_LIGHT) + .with_fg(theme::GREY) + .with_align(Alignment2D::CENTER_LEFT) .render(target); + + // hint + let icon_pos = Point::new(24, 398); + let link_pos = icon_pos + Offset::x(theme::ICON_INFO.toif.width() + 12); + let (hint_color, hint_text) = if self.ble_paired { + (theme::GREEN_LIME, "Trezor is paired") + } else { + (theme::GREY, "trezor.io/start") + }; + shape::ToifImage::new(icon_pos, theme::ICON_INFO.toif) + .with_fg(hint_color) + .with_align(Alignment2D::BOTTOM_LEFT) + .render(target); + shape::Text::new(link_pos, hint_text, fonts::FONT_SATOSHI_MEDIUM_26) + .with_fg(hint_color) + .render(target); + + self.action_bar.render(target); } } diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs b/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs index 06cfb814b9..bc8ce0b400 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/bootloader.rs @@ -9,8 +9,8 @@ use super::{ component::{ButtonStyle, ButtonStyleSheet}, fonts, }, - BLACK, BLUE, GREY, GREY_DARK, GREY_EXTRA_LIGHT, GREY_LIGHT, GREY_SUPER_DARK, ORANGE, RED, - WHITE, + BLACK, BLUE, GREEN_LIGHT, GREY, GREY_DARK, GREY_EXTRA_LIGHT, GREY_LIGHT, GREY_SUPER_DARK, + ORANGE, RED, WHITE, }; pub const BLD_BG: Color = BLACK; @@ -181,6 +181,34 @@ pub fn button_bld_menu_danger() -> ButtonStyleSheet { } } +/// Button style for the welcome screen +pub fn button_welcome_screen() -> ButtonStyleSheet { + ButtonStyleSheet { + normal: &ButtonStyle { + font: fonts::FONT_SATOSHI_MEDIUM_26, + text_color: GREY_LIGHT, + button_color: BLD_BG, + icon_color: GREY_LIGHT, + background_color: BLD_BG, + }, + active: &ButtonStyle { + font: fonts::FONT_SATOSHI_MEDIUM_26, + text_color: GREY_EXTRA_LIGHT, + button_color: GREY_SUPER_DARK, + icon_color: GREY_EXTRA_LIGHT, + background_color: BLD_BG, + }, + // used for the "Trezor is paired" button/footer + disabled: &ButtonStyle { + font: fonts::FONT_SATOSHI_MEDIUM_26, + text_color: GREEN_LIGHT, + button_color: BLD_BG, + icon_color: GREEN_LIGHT, + background_color: BLD_FG, + }, + } +} + pub const fn text_title(bg: Color) -> TextStyle { TextStyle::new(fonts::FONT_SATOSHI_MEDIUM_26, GREY, bg, GREY, GREY) } diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs index ebf625a09b..f97a79152e 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs @@ -133,8 +133,6 @@ impl BootloaderLayoutType for BootloaderLayout { } fn init_welcome() -> Self { - // TODO: different UI. needs to decide based on some host already paired: - // peer_count() > 0 let screen = BldWelcomeScreen::new(); Self::Welcome(screen) }