1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-26 01:42:34 +00:00

feat(eckhart): improve Homescreen styling

- correct Homebar icons
- colors and gradients
- LED usage
- Hint changes: add pad and cleanup some unused code

[no changelog]
This commit is contained in:
obrusvit 2025-06-11 21:58:02 +02:00 committed by Vít Obrusník
parent 2196a5f785
commit af02e5e6a2
7 changed files with 8921 additions and 8910 deletions

View File

@ -528,7 +528,7 @@ impl Button {
ButtonContent::HomeBar(text) => { ButtonContent::HomeBar(text) => {
let baseline = self.area.center(); let baseline = self.area.center();
if let Some(text) = text { if let Some(text) = text {
const OFFSET_Y: Offset = Offset::y(25); const OFFSET_Y: Offset = Offset::y(16);
text.map(|text| { text.map(|text| {
shape::Text::new(baseline, text, stylesheet.font) shape::Text::new(baseline, text, stylesheet.font)
.with_fg(stylesheet.text_color) .with_fg(stylesheet.text_color)
@ -536,23 +536,14 @@ impl Button {
.with_alpha(alpha) .with_alpha(alpha)
.render(target); .render(target);
}); });
shape::ToifImage::new( shape::ToifImage::new(self.area.center() + OFFSET_Y, theme::ICON_MINUS.toif)
self.area.center() + OFFSET_Y, .with_fg(stylesheet.icon_color)
theme::ICON_DASH_HORIZONTAL.toif,
)
.with_fg(stylesheet.icon_color)
.with_align(Alignment2D::CENTER)
.render(target);
} else {
// double dash icon in the middle
const OFFSET_Y: Offset = Offset::y(5);
shape::ToifImage::new(baseline - OFFSET_Y, theme::ICON_DASH_HORIZONTAL.toif)
.with_fg(theme::GREY_LIGHT)
.with_align(Alignment2D::CENTER) .with_align(Alignment2D::CENTER)
.render(target); .render(target);
} else {
shape::ToifImage::new(baseline + OFFSET_Y, theme::ICON_DASH_HORIZONTAL.toif) // Menu icon in the middle
.with_fg(theme::GREY_LIGHT) shape::ToifImage::new(baseline, theme::ICON_MENU.toif)
.with_fg(stylesheet.icon_color)
.with_align(Alignment2D::CENTER) .with_align(Alignment2D::CENTER)
.render(target); .render(target);
} }

View File

@ -1,10 +1,10 @@
use crate::{ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{text::TextStyle, Component, Event, EventCtx, Label, Never}, component::{text::TextStyle, Component, Event, EventCtx, Label, Never, Pad},
constant::screen, constant::screen,
display::{Color, Icon}, display::{Color, Icon},
geometry::{Alignment, Alignment2D, Direction, Insets, Offset, Point, Rect}, geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
shape::{self, Renderer, Text}, shape::{self, Renderer, Text},
util::Pager, util::Pager,
}, },
@ -20,12 +20,9 @@ use super::{super::fonts, theme};
/// The instruction has adaptive height, depending on the text length. The /// The instruction has adaptive height, depending on the text length. The
/// PageCounter is always of minimal component height (40px). /// PageCounter is always of minimal component height (40px).
pub struct Hint<'a> { pub struct Hint<'a> {
area: Rect, content_area: Rect,
content: HintContent<'a>, content: HintContent<'a>,
swipe_allow_up: bool, pad: Pad,
swipe_allow_down: bool,
progress: i16,
dir: Direction,
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -44,12 +41,9 @@ impl<'a> Hint<'a> {
fn from_content(content: HintContent<'a>) -> Self { fn from_content(content: HintContent<'a>) -> Self {
Self { Self {
area: Rect::zero(), content_area: Rect::zero(),
content, content,
swipe_allow_down: false, pad: Pad::with_background(theme::BG),
swipe_allow_up: false,
progress: 0,
dir: Direction::Up,
} }
} }
@ -69,11 +63,21 @@ impl<'a> Hint<'a> {
Self::from_content(HintContent::Instruction(instruction_component)) Self::from_content(HintContent::Instruction(instruction_component))
} }
pub fn new_warning<T: Into<TString<'static>>>(text: T) -> Self { pub fn new_warning_neutral<T: Into<TString<'static>>>(text: T) -> Self {
let instruction_component = Instruction::new(
text.into(),
theme::GREY_LIGHT,
Some(theme::ICON_WARNING),
Some(theme::YELLOW),
);
Self::from_content(HintContent::Instruction(instruction_component))
}
pub fn new_warning_caution<T: Into<TString<'static>>>(text: T) -> Self {
let instruction_component = Instruction::new( let instruction_component = Instruction::new(
text.into(), text.into(),
theme::GREY, theme::GREY,
Some(theme::ICON_INFO), Some(theme::ICON_WARNING),
Some(theme::ORANGE), Some(theme::ORANGE),
); );
Self::from_content(HintContent::Instruction(instruction_component)) Self::from_content(HintContent::Instruction(instruction_component))
@ -93,25 +97,9 @@ impl<'a> Hint<'a> {
Self::from_content(HintContent::PageCounter(PageCounter::new())) Self::from_content(HintContent::PageCounter(PageCounter::new()))
} }
pub fn with_swipe(self, swipe_direction: Direction) -> Self {
match swipe_direction {
Direction::Up => Self {
swipe_allow_up: true,
..self
},
Direction::Down => Self {
swipe_allow_down: true,
..self
},
_ => self,
}
}
pub fn update(&mut self, pager: Pager) { pub fn update(&mut self, pager: Pager) {
if let HintContent::PageCounter(counter) = &mut self.content { if let HintContent::PageCounter(counter) = &mut self.content {
counter.update(pager); counter.update(pager);
self.swipe_allow_down = counter.pager.is_first();
self.swipe_allow_up = counter.pager.is_last();
} }
} }
@ -128,6 +116,12 @@ impl<'a> Component for Hint<'a> {
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
debug_assert!(bounds.width() == screen().width()); debug_assert!(bounds.width() == screen().width());
debug_assert!(bounds.height() == self.height()); debug_assert!(bounds.height() == self.height());
let pad_area = bounds
.inset(Insets::top(Self::HINT_INSETS.top))
.inset(Insets::bottom(Self::HINT_INSETS.bottom));
self.pad.place(pad_area);
let bounds = bounds.inset(Self::HINT_INSETS); let bounds = bounds.inset(Self::HINT_INSETS);
if let HintContent::Instruction(instruction) = &mut self.content { if let HintContent::Instruction(instruction) = &mut self.content {
@ -137,8 +131,8 @@ impl<'a> Component for Hint<'a> {
}; };
instruction.label.place(text_area); instruction.label.place(text_area);
} }
self.area = bounds; self.content_area = bounds;
self.area self.content_area
} }
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> { fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
@ -146,7 +140,8 @@ impl<'a> Component for Hint<'a> {
} }
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.content.render(self.area, target); self.pad.render(target);
self.content.render(self.content_area, target);
} }
} }
@ -183,8 +178,6 @@ struct Instruction<'a> {
impl<'a> Instruction<'a> { impl<'a> Instruction<'a> {
/// default style for instruction text /// default style for instruction text
const STYLE_INSTRUCTION: &'static TextStyle = &theme::firmware::TEXT_SMALL; const STYLE_INSTRUCTION: &'static TextStyle = &theme::firmware::TEXT_SMALL;
/// margin between icon and text
const ICON_OFFSET: Offset = Offset::x(24); // [px]
fn new( fn new(
text: TString<'a>, text: TString<'a>,
@ -205,7 +198,7 @@ impl<'a> Instruction<'a> {
/// Calculates the width needed for the icon /// Calculates the width needed for the icon
fn icon_width(&self) -> i16 { fn icon_width(&self) -> i16 {
self.icon self.icon
.map_or(0, |icon| icon.toif.width() + Self::ICON_OFFSET.x) .map_or(0, |icon| icon.toif.width() + theme::PADDING)
} }
/// Calculates the height needed for the Instruction text to be rendered. /// Calculates the height needed for the Instruction text to be rendered.

View File

@ -1,3 +1,6 @@
#[cfg(feature = "rgb_led")]
use crate::trezorhal::rgb_led;
use crate::{ use crate::{
error::Error, error::Error,
io::BinaryData, io::BinaryData,
@ -69,33 +72,23 @@ impl Homescreen {
let shadow = image.is_some(); let shadow = image.is_some();
// Notification // Notification
// TODO: better notification handling
let mut notification_level = 4; let mut notification_level = 4;
let mut hint = None; let (led_color, hint) = match notification {
let mut led_color; Some((text, level)) => {
if let Some((text, level)) = notification { notification_level = level;
notification_level = level; let (led_color, hint) = Self::get_notification_display(level, text);
if notification_level == 0 { (Some(led_color), Some(hint))
led_color = Some(theme::RED);
hint = Some(Hint::new_warning_danger(text));
} else {
led_color = Some(theme::YELLOW);
hint = Some(Hint::new_instruction(text, Some(theme::ICON_INFO)));
} }
} else if locked && coinjoin_authorized { None if locked && coinjoin_authorized => (
led_color = Some(theme::GREEN_LIME); Some(theme::GREEN_LIME),
hint = Some(Hint::new_instruction_green( Some(Hint::new_instruction_green(
TR::coinjoin__do_not_disconnect, TR::coinjoin__do_not_disconnect,
Some(theme::ICON_INFO), Some(theme::ICON_INFO),
)); )),
} else { ),
led_color = Some(theme::GREY_LIGHT); None => (None, None),
}; };
if locked {
led_color = None;
}
Ok(Self { Ok(Self {
label: HomeLabel::new(label, shadow), label: HomeLabel::new(label, shadow),
hint, hint,
@ -126,6 +119,22 @@ impl Homescreen {
ButtonContent::HomeBar(text) ButtonContent::HomeBar(text)
} }
fn get_notification_display(level: u8, text: TString<'static>) -> (Color, Hint<'static>) {
match level {
0 => (theme::RED, Hint::new_warning_danger(text)),
1 => (theme::YELLOW, Hint::new_warning_neutral(text)),
2 => (theme::BLUE, Hint::new_instruction(text, None)),
3 => (
theme::GREEN_LIGHT,
Hint::new_instruction_green(text, Some(theme::ICON_INFO)),
),
_ => (
theme::GREY_LIGHT,
Hint::new_instruction(text, Some(theme::ICON_INFO)),
),
}
}
fn event_fuel_gauge(&mut self, ctx: &mut EventCtx, event: Event) { fn event_fuel_gauge(&mut self, ctx: &mut EventCtx, event: Event) {
if animation_disabled() { if animation_disabled() {
return; return;
@ -151,6 +160,14 @@ impl Homescreen {
} }
} }
impl Drop for Homescreen {
fn drop(&mut self) {
// Turn off the LED when homescreen is destroyed
#[cfg(feature = "rgb_led")]
rgb_led::set_color(0);
}
}
impl Component for Homescreen { impl Component for Homescreen {
type Msg = HomescreenMsg; type Msg = HomescreenMsg;
@ -210,6 +227,13 @@ impl Component for Homescreen {
if self.fuel_gauge.should_be_shown() { if self.fuel_gauge.should_be_shown() {
self.fuel_gauge.render(target); self.fuel_gauge.render(target);
} }
#[cfg(feature = "rgb_led")]
if let Some(rgb_led) = self.led_color {
rgb_led::set_color(rgb_led.to_u32());
} else {
rgb_led::set_color(0);
}
} }
} }

View File

@ -373,12 +373,12 @@ pub const fn menu_item_title_red() -> ButtonStyleSheet {
} }
macro_rules! button_homebar_style { macro_rules! button_homebar_style {
($text_color:expr, $icon_color:expr) => { ($button_color:expr, $icon_color:expr) => {
ButtonStyleSheet { ButtonStyleSheet {
normal: &ButtonStyle { normal: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26, font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: $text_color, text_color: GREY_LIGHT,
button_color: BG, button_color: $button_color,
icon_color: $icon_color, icon_color: $icon_color,
background_color: BG, background_color: BG,
}, },
@ -389,6 +389,7 @@ macro_rules! button_homebar_style {
icon_color: GREY_LIGHT, icon_color: GREY_LIGHT,
background_color: GREY_SUPER_DARK, background_color: GREY_SUPER_DARK,
}, },
// unused
disabled: &ButtonStyle { disabled: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26, font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: GREY_LIGHT, text_color: GREY_LIGHT,
@ -399,14 +400,14 @@ macro_rules! button_homebar_style {
} }
}; };
} }
pub const fn button_homebar_style(level: u8) -> ButtonStyleSheet { pub const fn button_homebar_style(notification_level: u8) -> ButtonStyleSheet {
// NOTE: 0 is the highest severity. // NOTE: 0 is the highest severity.
match level { match notification_level {
4 => button_homebar_style!(GREY_LIGHT, GREY_LIGHT), 0 => button_homebar_style!(ORANGE_SUPER_DARK, RED),
3 => button_homebar_style!(GREY_LIGHT, GREEN_LIME), 1 => button_homebar_style!(YELLOW_DARK, GREY_LIGHT),
2 => button_homebar_style!(GREY_LIGHT, YELLOW), 2 => button_homebar_style!(GREY_SUPER_DARK, GREY_LIGHT),
1 => button_homebar_style!(GREY_LIGHT, YELLOW), 3 => button_homebar_style!(GREEN_DARK, GREY_LIGHT),
_ => button_homebar_style!(RED, RED), _ => button_homebar_style!(GREY_EXTRA_DARK, GREY_LIGHT),
} }
} }

View File

@ -40,8 +40,10 @@ pub const ORANGE: Color = Color::rgb(0xFF, 0x63, 0x30);
pub const ORANGE_DIMMED: Color = Color::rgb(0x9E, 0x57, 0x42); pub const ORANGE_DIMMED: Color = Color::rgb(0x9E, 0x57, 0x42);
pub const ORANGE_DARK: Color = Color::rgb(0x18, 0x0C, 0x0A); pub const ORANGE_DARK: Color = Color::rgb(0x18, 0x0C, 0x0A);
pub const ORANGE_EXTRA_DARK: Color = Color::rgb(0x12, 0x07, 0x04); pub const ORANGE_EXTRA_DARK: Color = Color::rgb(0x12, 0x07, 0x04);
pub const ORANGE_SUPER_DARK: Color = Color::rgb(0x2A, 0x0A, 0x00); // Homescreen gradient
pub const YELLOW: Color = Color::rgb(0xFF, 0xE4, 0x58); pub const YELLOW: Color = Color::rgb(0xFF, 0xE4, 0x58);
pub const YELLOW_DARK: Color = Color::rgb(0x21, 0x1E, 0x0C); // Homescreen gradient
pub const BLUE: Color = Color::rgb(0x00, 0x46, 0xFF); pub const BLUE: Color = Color::rgb(0x00, 0x46, 0xFF);
@ -50,11 +52,11 @@ 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);
// Common constants // Common constants
pub const PADDING: i16 = 24; // px pub const PADDING: i16 = 24; // [px]
pub const HEADER_HEIGHT: i16 = 96; // [px] pub const HEADER_HEIGHT: i16 = 96; // [px]
pub const SIDE_INSETS: Insets = Insets::sides(PADDING); pub const SIDE_INSETS: Insets = Insets::sides(PADDING);
pub const ACTION_BAR_HEIGHT: i16 = 90; // [px] pub const ACTION_BAR_HEIGHT: i16 = 90; // [px]
pub const PARAGRAPHS_SPACING: i16 = 12; // px pub const PARAGRAPHS_SPACING: i16 = 12; // [px]
pub const TEXT_VERTICAL_SPACING: i16 = 24; // [px] pub const TEXT_VERTICAL_SPACING: i16 = 24; // [px]
// Tile pattern grid constants // Tile pattern grid constants

View File

@ -454,7 +454,7 @@ impl FirmwareUI for UIEckhart {
screen = screen.with_hint(Hint::new_page_counter()); screen = screen.with_hint(Hint::new_page_counter());
} }
if let Some(warning_footer) = warning_footer { if let Some(warning_footer) = warning_footer {
screen = screen.with_hint(Hint::new_warning(warning_footer)); screen = screen.with_hint(Hint::new_warning_caution(warning_footer));
} }
LayoutObj::new(screen) LayoutObj::new(screen)
} }

File diff suppressed because it is too large Load Diff