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

View File

@ -1,10 +1,10 @@
use crate::{
strutil::TString,
ui::{
component::{text::TextStyle, Component, Event, EventCtx, Label, Never},
component::{text::TextStyle, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
display::{Color, Icon},
geometry::{Alignment, Alignment2D, Direction, Insets, Offset, Point, Rect},
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
shape::{self, Renderer, Text},
util::Pager,
},
@ -20,12 +20,9 @@ use super::{super::fonts, theme};
/// The instruction has adaptive height, depending on the text length. The
/// PageCounter is always of minimal component height (40px).
pub struct Hint<'a> {
area: Rect,
content_area: Rect,
content: HintContent<'a>,
swipe_allow_up: bool,
swipe_allow_down: bool,
progress: i16,
dir: Direction,
pad: Pad,
}
#[allow(clippy::large_enum_variant)]
@ -44,12 +41,9 @@ impl<'a> Hint<'a> {
fn from_content(content: HintContent<'a>) -> Self {
Self {
area: Rect::zero(),
content_area: Rect::zero(),
content,
swipe_allow_down: false,
swipe_allow_up: false,
progress: 0,
dir: Direction::Up,
pad: Pad::with_background(theme::BG),
}
}
@ -69,11 +63,21 @@ impl<'a> Hint<'a> {
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(
text.into(),
theme::GREY,
Some(theme::ICON_INFO),
Some(theme::ICON_WARNING),
Some(theme::ORANGE),
);
Self::from_content(HintContent::Instruction(instruction_component))
@ -93,25 +97,9 @@ impl<'a> Hint<'a> {
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) {
if let HintContent::PageCounter(counter) = &mut self.content {
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 {
debug_assert!(bounds.width() == screen().width());
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);
if let HintContent::Instruction(instruction) = &mut self.content {
@ -137,8 +131,8 @@ impl<'a> Component for Hint<'a> {
};
instruction.label.place(text_area);
}
self.area = bounds;
self.area
self.content_area = bounds;
self.content_area
}
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>) {
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> {
/// default style for instruction text
const STYLE_INSTRUCTION: &'static TextStyle = &theme::firmware::TEXT_SMALL;
/// margin between icon and text
const ICON_OFFSET: Offset = Offset::x(24); // [px]
fn new(
text: TString<'a>,
@ -205,7 +198,7 @@ impl<'a> Instruction<'a> {
/// Calculates the width needed for the icon
fn icon_width(&self) -> i16 {
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.

View File

@ -1,3 +1,6 @@
#[cfg(feature = "rgb_led")]
use crate::trezorhal::rgb_led;
use crate::{
error::Error,
io::BinaryData,
@ -69,33 +72,23 @@ impl Homescreen {
let shadow = image.is_some();
// Notification
// TODO: better notification handling
let mut notification_level = 4;
let mut hint = None;
let mut led_color;
if let Some((text, level)) = notification {
notification_level = level;
if notification_level == 0 {
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)));
let (led_color, hint) = match notification {
Some((text, level)) => {
notification_level = level;
let (led_color, hint) = Self::get_notification_display(level, text);
(Some(led_color), Some(hint))
}
} else if locked && coinjoin_authorized {
led_color = Some(theme::GREEN_LIME);
hint = Some(Hint::new_instruction_green(
TR::coinjoin__do_not_disconnect,
Some(theme::ICON_INFO),
));
} else {
led_color = Some(theme::GREY_LIGHT);
None if locked && coinjoin_authorized => (
Some(theme::GREEN_LIME),
Some(Hint::new_instruction_green(
TR::coinjoin__do_not_disconnect,
Some(theme::ICON_INFO),
)),
),
None => (None, None),
};
if locked {
led_color = None;
}
Ok(Self {
label: HomeLabel::new(label, shadow),
hint,
@ -126,6 +119,22 @@ impl Homescreen {
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) {
if animation_disabled() {
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 {
type Msg = HomescreenMsg;
@ -210,6 +227,13 @@ impl Component for Homescreen {
if self.fuel_gauge.should_be_shown() {
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 {
($text_color:expr, $icon_color:expr) => {
($button_color:expr, $icon_color:expr) => {
ButtonStyleSheet {
normal: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: $text_color,
button_color: BG,
text_color: GREY_LIGHT,
button_color: $button_color,
icon_color: $icon_color,
background_color: BG,
},
@ -389,6 +389,7 @@ macro_rules! button_homebar_style {
icon_color: GREY_LIGHT,
background_color: GREY_SUPER_DARK,
},
// unused
disabled: &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26,
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.
match level {
4 => button_homebar_style!(GREY_LIGHT, GREY_LIGHT),
3 => button_homebar_style!(GREY_LIGHT, GREEN_LIME),
2 => button_homebar_style!(GREY_LIGHT, YELLOW),
1 => button_homebar_style!(GREY_LIGHT, YELLOW),
_ => button_homebar_style!(RED, RED),
match notification_level {
0 => button_homebar_style!(ORANGE_SUPER_DARK, RED),
1 => button_homebar_style!(YELLOW_DARK, GREY_LIGHT),
2 => button_homebar_style!(GREY_SUPER_DARK, GREY_LIGHT),
3 => button_homebar_style!(GREEN_DARK, GREY_LIGHT),
_ => 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_DARK: Color = Color::rgb(0x18, 0x0C, 0x0A);
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_DARK: Color = Color::rgb(0x21, 0x1E, 0x0C); // Homescreen gradient
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);
// Common constants
pub const PADDING: i16 = 24; // px
pub const PADDING: i16 = 24; // [px]
pub const HEADER_HEIGHT: i16 = 96; // [px]
pub const SIDE_INSETS: Insets = Insets::sides(PADDING);
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]
// Tile pattern grid constants

View File

@ -454,7 +454,7 @@ impl FirmwareUI for UIEckhart {
screen = screen.with_hint(Hint::new_page_counter());
}
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)
}

File diff suppressed because it is too large Load Diff