1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-04 04:48:48 +00:00

feat(eckhart): default homescreen

This commit is contained in:
obrusvit 2025-03-23 23:55:05 +01:00 committed by M1nd3r
parent 65df15fbdc
commit 3934d5d3c8
6 changed files with 139 additions and 25 deletions

View File

@ -5,18 +5,22 @@ use crate::{
translations::TR, translations::TR,
ui::{ ui::{
component::{text::TextStyle, Component, Event, EventCtx, Label, Never}, component::{text::TextStyle, Component, Event, EventCtx, Label, Never},
display::image::ImageInfo, display::{image::ImageInfo, Color},
geometry::{Insets, Offset, Rect}, geometry::{Alignment2D, Grid, Insets, Offset, Point, Rect},
layout::util::get_user_custom_image, layout::util::get_user_custom_image,
lerp::Lerp,
shape::{self, Renderer}, shape::{self, Renderer},
util::{animation_disabled, Pager}, util::animation_disabled,
}, },
}; };
use super::{ use super::{
super::{component::Button, fonts}, super::{
component::{Button, ButtonMsg},
fonts,
},
constant::{HEIGHT, SCREEN, WIDTH}, constant::{HEIGHT, SCREEN, WIDTH},
theme::{self, firmware::button_homebar_style, BLACK, GREEN_DARK, GREEN_EXTRA_DARK}, theme::{self, firmware::button_homebar_style, BG, BLACK, GREY_EXTRA_DARK},
ActionBar, ActionBarMsg, Hint, HoldToConfirmAnim, ActionBar, ActionBarMsg, Hint, HoldToConfirmAnim,
}; };
@ -30,6 +34,8 @@ pub struct Homescreen {
action_bar: ActionBar, action_bar: ActionBar,
/// Background image /// Background image
image: Option<BinaryData<'static>>, image: Option<BinaryData<'static>>,
/// LED color
led_color: Option<Color>,
/// Whether the PIN is set and device can be locked /// Whether the PIN is set and device can be locked
lockable: bool, lockable: bool,
/// Whether the homescreen is locked /// Whether the homescreen is locked
@ -57,23 +63,33 @@ impl Homescreen {
let image = get_homescreen_image(); let image = get_homescreen_image();
// Notification // Notification
// TODO: better notification handling
let mut notification_level = 4; let mut notification_level = 4;
let hint = if let Some((text, level)) = notification { let mut hint = None;
let mut led_color;
if let Some((text, level)) = notification {
notification_level = level; notification_level = level;
if notification_level == 0 { if notification_level == 0 {
Some(Hint::new_warning_severe(text)) led_color = Some(theme::RED);
hint = Some(Hint::new_warning_severe(text));
} else { } else {
Some(Hint::new_instruction(text, Some(theme::ICON_INFO))) led_color = Some(theme::YELLOW);
hint = Some(Hint::new_instruction(text, Some(theme::ICON_INFO)));
} }
} else if locked && coinjoin_authorized { } else if locked && coinjoin_authorized {
Some(Hint::new_instruction_green( led_color = Some(theme::GREEN_LIME);
hint = Some(Hint::new_instruction_green(
TR::coinjoin__do_not_disconnect, TR::coinjoin__do_not_disconnect,
Some(theme::ICON_INFO), Some(theme::ICON_INFO),
)) ));
} else { } else {
None led_color = Some(theme::GREY_LIGHT);
}; };
if locked {
led_color = None;
}
// ActionBar button // ActionBar button
let button_style = button_homebar_style(notification_level); let button_style = button_homebar_style(notification_level);
let button = if bootscreen { let button = if bootscreen {
@ -94,8 +110,7 @@ impl Homescreen {
Some( Some(
HoldToConfirmAnim::new() HoldToConfirmAnim::new()
.with_color(theme::GREY_LIGHT) .with_color(theme::GREY_LIGHT)
.with_duration(lock_duration) .with_duration(lock_duration),
.with_header_overlay(TR::progress__locking_device.into()),
) )
} else { } else {
None None
@ -106,6 +121,7 @@ impl Homescreen {
hint, hint,
action_bar: ActionBar::new_single(button), action_bar: ActionBar::new_single(button),
image, image,
led_color,
lockable, lockable,
locked, locked,
virtual_locking_button: Button::empty().with_long_press(lock_duration), virtual_locking_button: Button::empty().with_long_press(lock_duration),
@ -202,7 +218,7 @@ impl Component for Homescreen {
shape::JpegImage::new_image(SCREEN.top_left(), image).render(target); shape::JpegImage::new_image(SCREEN.top_left(), image).render(target);
} }
} else { } else {
render_default_hs(target); render_default_hs(target, self.led_color);
} }
self.label.render(target); self.label.render(target);
self.hint.render(target); self.hint.render(target);
@ -268,20 +284,108 @@ pub fn check_homescreen_format(image: BinaryData) -> bool {
} }
} }
fn render_default_hs<'a>(target: &mut impl Renderer<'a>) { fn render_default_hs<'a>(target: &mut impl Renderer<'a>, led_color: Option<Color>) {
const DEFAULT_HS_TILE_ROWS: usize = 4;
const DEFAULT_HS_TILE_COLS: usize = 4;
const DEFAULT_HS_AREA: Rect = SCREEN.inset(Insets::bottom(140));
const DEFAULT_HS_GRID: Grid =
Grid::new(DEFAULT_HS_AREA, DEFAULT_HS_TILE_ROWS, DEFAULT_HS_TILE_COLS);
const DEFAULT_HS_TILES_2: [(usize, usize); 9] = [
(0, 0),
(1, 0),
(1, 3),
(2, 3),
(3, 2),
(3, 3),
(4, 0),
(4, 2),
(4, 3),
];
// Layer 1: Base Solid Colour
shape::Bar::new(SCREEN) shape::Bar::new(SCREEN)
.with_fg(theme::BG) .with_bg(GREY_EXTRA_DARK)
.with_bg(theme::BG)
.render(target); .render(target);
shape::Circle::new(SCREEN.center(), 48) // Layer 2: Base Gradient overlay
.with_fg(GREEN_DARK) for y in SCREEN.y0..SCREEN.y1 {
.with_thickness(4) let slice = Rect::new(Point::new(SCREEN.x0, y), Point::new(SCREEN.x1, y + 1));
.render(target); let factor = (y - SCREEN.y0) as f32 / SCREEN.height() as f32;
shape::Circle::new(SCREEN.center(), 42) shape::Bar::new(slice)
.with_fg(GREEN_EXTRA_DARK) .with_bg(BG)
.with_thickness(4) .with_alpha(u8::lerp(u8::MIN, u8::MAX, factor))
.render(target); .render(target);
}
// Layer 3: (Optional) LED lightning simulation
if let Some(color) = led_color {
render_led_simulation(color, target);
}
// Layer 4: Tile pattern
// TODO: improve frame rate
for row in 0..DEFAULT_HS_TILE_ROWS {
for col in 0..DEFAULT_HS_TILE_COLS {
let tile_area = DEFAULT_HS_GRID.row_col(row, col);
let icon = if DEFAULT_HS_TILES_2.contains(&(row, col)) {
theme::ICON_HS_TILE_2.toif
} else {
theme::ICON_HS_TILE_1.toif
};
shape::ToifImage::new(tile_area.top_left(), icon)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(BLACK)
.render(target);
}
}
}
fn render_led_simulation<'a>(color: Color, target: &mut impl Renderer<'a>) {
const Y_MAX: i16 = SCREEN.y1 - theme::ACTION_BAR_HEIGHT;
const Y_RANGE: i16 = Y_MAX - SCREEN.y0;
const X_MID: i16 = SCREEN.x0 + SCREEN.width() / 2;
const X_HALF_WIDTH: f32 = (SCREEN.width() / 2) as f32;
// Vertical gradient (color intensity fading from bottom to top)
#[allow(clippy::reversed_empty_ranges)] // clippy fails here for T3B1 which has smaller screen
for y in SCREEN.y0..Y_MAX {
let factor = (y - SCREEN.y0) as f32 / Y_RANGE as f32;
let slice = Rect::new(Point::new(SCREEN.x0, y), Point::new(SCREEN.x1, y + 1));
// Gradient 1 (Overall intensity: 35%)
// Stops: 0%, 40%
// Opacity: 100%, 20%
let factor_grad_1 = (factor / 0.4).clamp(0.2, 1.0);
shape::Bar::new(slice)
.with_bg(color)
.with_alpha(u8::lerp(89, u8::MIN, factor_grad_1))
.render(target);
// Gradient 2 (Overall intensity: 70%)
// Stops: 2%, 63%
// Opacity: 100%, 0%
let factor_grad_2 = ((factor - 0.02) / (0.63 - 0.02)).clamp(0.0, 1.0);
let alpha = u8::lerp(179, u8::MIN, factor_grad_2);
shape::Bar::new(slice)
.with_bg(color)
.with_alpha(alpha)
.render(target);
}
// Horizontal gradient (transparency increasing toward center)
for x in SCREEN.x0..SCREEN.x1 {
const WIDTH: i16 = SCREEN.width();
let slice = Rect::new(Point::new(x, SCREEN.y0), Point::new(x + 1, Y_MAX));
// Gradient 3
// Calculate distance from center as a normalized factor (0 at center, 1 at
// edges)
let dist_from_mid = (x - X_MID).abs() as f32 / X_HALF_WIDTH;
shape::Bar::new(slice)
.with_bg(BG)
.with_alpha(u8::lerp(u8::MIN, u8::MAX, dist_from_mid))
.render(target);
}
} }
fn get_homescreen_image() -> Option<BinaryData<'static>> { fn get_homescreen_image() -> Option<BinaryData<'static>> {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 987 B

View File

@ -103,6 +103,16 @@ include_icon!(ICON_BORDER_TR, "layout_eckhart/res/border/TR.toif");
include_icon!(ICON_PLUS, "layout_eckhart/res/plus.toif"); include_icon!(ICON_PLUS, "layout_eckhart/res/plus.toif");
include_icon!(ICON_MINUS, "layout_eckhart/res/minus.toif"); include_icon!(ICON_MINUS, "layout_eckhart/res/minus.toif");
// Icon tiles for default homescreen
include_icon!(
ICON_HS_TILE_1,
"layout_eckhart/res/defaut_homescreen/hs_tile1.toif"
);
include_icon!(
ICON_HS_TILE_2,
"layout_eckhart/res/defaut_homescreen/hs_tile2.toif"
);
// Common text styles and button styles must use fonts accessible from both // Common text styles and button styles must use fonts accessible from both
// bootloader and firmware // bootloader and firmware