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:
parent
2196a5f785
commit
af02e5e6a2
@ -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,
|
||||
)
|
||||
shape::ToifImage::new(self.area.center() + OFFSET_Y, theme::ICON_MINUS.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)
|
||||
.render(target);
|
||||
|
||||
shape::ToifImage::new(baseline + OFFSET_Y, theme::ICON_DASH_HORIZONTAL.toif)
|
||||
.with_fg(theme::GREY_LIGHT)
|
||||
// Menu icon in the middle
|
||||
shape::ToifImage::new(baseline, theme::ICON_MENU.toif)
|
||||
.with_fg(stylesheet.icon_color)
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.render(target);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
let (led_color, hint) = match notification {
|
||||
Some((text, level)) => {
|
||||
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) = 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(
|
||||
None if locked && coinjoin_authorized => (
|
||||
Some(theme::GREEN_LIME),
|
||||
Some(Hint::new_instruction_green(
|
||||
TR::coinjoin__do_not_disconnect,
|
||||
Some(theme::ICON_INFO),
|
||||
));
|
||||
} else {
|
||||
led_color = Some(theme::GREY_LIGHT);
|
||||
)),
|
||||
),
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user