1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-03 12:00:59 +00:00

feat(core/rust): improve the painting of TR's buttons + new inverse design

[no changelog]
This commit is contained in:
grdddj 2023-06-16 15:40:00 +02:00 committed by Jiří Musil
parent a6316f964e
commit b420064c08
2 changed files with 85 additions and 114 deletions

View File

@ -5,7 +5,7 @@ use crate::{
component::{Component, Event, EventCtx, Never}, component::{Component, Event, EventCtx, Never},
constant, constant,
display::{self, Color, Font, Icon}, display::{self, Color, Font, Icon},
geometry::{Alignment2D, Insets, Offset, Point, Rect}, geometry::{Alignment2D, Offset, Point, Rect},
}, },
}; };
@ -115,49 +115,40 @@ where
let button_width = if let Some(width) = style.fixed_width { let button_width = if let Some(width) = style.fixed_width {
width width
} else { } else {
let outline = if style.with_outline { match &self.content {
theme::BUTTON_OUTLINE ButtonContent::Text(text) => {
} else { let text_width = style.font.visible_text_width(text.as_ref());
0 if style.with_outline {
}; text_width + 2 * theme::BUTTON_OUTLINE
let content_width = match &self.content { } else if style.with_arms {
ButtonContent::Text(text) => style.font.visible_text_width(text.as_ref()), text_width + 2 * theme::ARMS_MARGIN
ButtonContent::Icon(icon) => icon.toif.width(), } else {
}; text_width
content_width + 2 * outline }
}; }
ButtonContent::Icon(icon) => {
// Button height may be adjusted for the icon without outline // When Icon does not have outline, hardcode its width
// Done to avoid highlighting bigger area than necessary when if style.with_outline {
// drawing the icon in active (black on white) state icon.toif.width() + 2 * theme::BUTTON_OUTLINE
let button_height = match &self.content { } else {
ButtonContent::Text(_) => theme::BUTTON_HEIGHT, theme::BUTTON_ICON_WIDTH
ButtonContent::Icon(icon) => { }
if style.with_outline {
theme::BUTTON_HEIGHT
} else {
icon.toif.height()
} }
} }
}; };
let button_bounds = self.bounds.split_bottom(button_height).1; let button_bounds = self.bounds.split_bottom(theme::BUTTON_HEIGHT).1;
let area = match self.pos { match self.pos {
ButtonPos::Left => button_bounds.split_left(button_width).0, ButtonPos::Left => button_bounds.split_left(button_width).0,
ButtonPos::Right => button_bounds.split_right(button_width).1, ButtonPos::Right => button_bounds.split_right(button_width).1,
ButtonPos::Middle => button_bounds.split_center(button_width).1, ButtonPos::Middle => button_bounds.split_center(button_width).1,
}; }
// Allowing for possible offset of the area from current style
area.translate(style.offset)
} }
/// Determine baseline point for the text. /// Determine baseline point for the text.
fn get_text_baseline(&self, style: &ButtonStyle) -> Point { fn get_text_baseline(&self, style: &ButtonStyle) -> Point {
// Arms and outline require the text to be elevated. // Arms and outline require the text to be elevated.
let offset_y = if style.with_arms { let offset_y = if style.with_outline || style.with_arms {
theme::BUTTON_ARMS
} else if style.with_outline {
theme::BUTTON_OUTLINE theme::BUTTON_OUTLINE
} else { } else {
0 0
@ -165,6 +156,8 @@ where
let offset_x = if style.with_outline { let offset_x = if style.with_outline {
theme::BUTTON_OUTLINE theme::BUTTON_OUTLINE
} else if style.with_arms {
theme::ARMS_MARGIN
} else { } else {
0 0
}; };
@ -191,51 +184,41 @@ where
fn paint(&mut self) { fn paint(&mut self) {
let style = self.style(); let style = self.style();
let text_color = style.text_color; let fg_color = style.text_color;
let background_color = text_color.negate(); let bg_color = fg_color.negate();
let mut area = self.get_current_area(); let area = self.get_current_area();
let inversed_colors = bg_color != theme::BG;
// Optionally display "arms" at both sides of content, or create // Filling the background (with 2-pixel rounding when applicable)
// a nice rounded outline around it. if inversed_colors {
// By default just fill the content background. display::rect_outline_rounded(area, bg_color, fg_color, 2);
if style.with_arms { display::rect_fill(area.shrink(1), bg_color);
const ARM_WIDTH: i16 = 15;
area = area.translate(Offset::y(1));
// Prepare space for both the arms and content with BG color.
// Arms are icons 10*6 pixels.
let area_to_fill = area.outset(Insets::sides(ARM_WIDTH));
display::rect_fill(area_to_fill, background_color);
display::rect_fill_corners(area_to_fill, theme::BG);
// Paint both arms.
// Baselines are adjusted to give space between text and icon.
// 2 px because 1px might lead to odd coordinate which can't be render
theme::ICON_ARM_LEFT.draw(
area.left_center() - Offset::x(2),
Alignment2D::TOP_RIGHT,
text_color,
background_color,
);
theme::ICON_ARM_RIGHT.draw(
area.right_center() + Offset::x(2),
Alignment2D::TOP_LEFT,
text_color,
background_color,
);
} else if style.with_outline { } else if style.with_outline {
if background_color == theme::BG { display::rect_outline_rounded(area, fg_color, bg_color, 2);
display::rect_outline_rounded(area, text_color, background_color, 2);
} else {
// With inverse colors having just radius of one, `rect_outline_rounded`
// is not suitable for inverse colors.
display::rect_fill(area, background_color);
display::rect_fill_corners(area, theme::BG);
}
} else { } else {
display::rect_fill(area, background_color); display::rect_fill(area, bg_color);
} }
// Optionally display "arms" at both sides of content - always in FG and BG
// colors (they are not inverted).
if style.with_arms {
// Putting them one pixel down to touch the bottom.
let arms_area = area.translate(Offset::y(1));
theme::ICON_ARM_LEFT.draw(
arms_area.left_center(),
Alignment2D::TOP_RIGHT,
theme::FG,
theme::BG,
);
theme::ICON_ARM_RIGHT.draw(
arms_area.right_center(),
Alignment2D::TOP_LEFT,
theme::FG,
theme::BG,
);
}
// Painting the content
match &self.content { match &self.content {
ButtonContent::Text(text) => { ButtonContent::Text(text) => {
display::text_left( display::text_left(
@ -243,37 +226,33 @@ where
- Offset::x(style.font.start_x_bearing(text.as_ref())), - Offset::x(style.font.start_x_bearing(text.as_ref())),
text.as_ref(), text.as_ref(),
style.font, style.font,
text_color, fg_color,
background_color, bg_color,
); );
} }
ButtonContent::Icon(icon) => { ButtonContent::Icon(icon) => {
// Allowing for possible offset of the area from current style
let icon_area = area.translate(style.offset);
if style.with_outline { if style.with_outline {
// Accounting for the 8*8 icon with empty left column and bottom row icon.draw(icon_area.center(), Alignment2D::CENTER, fg_color, bg_color);
// (which fits the outline nicely and symmetrically)
let center = area.center() + Offset::uniform(1);
icon.draw(center, Alignment2D::CENTER, text_color, background_color);
} else { } else {
// Positioning the icon in the corresponding corner/center // Positioning the icon in the corresponding corner/center
match self.pos { match self.pos {
ButtonPos::Left => icon.draw( ButtonPos::Left => icon.draw(
area.bottom_left(), icon_area.bottom_left(),
Alignment2D::BOTTOM_LEFT, Alignment2D::BOTTOM_LEFT,
text_color, fg_color,
background_color, bg_color,
), ),
ButtonPos::Right => icon.draw( ButtonPos::Right => icon.draw(
area.bottom_right(), icon_area.bottom_right(),
Alignment2D::BOTTOM_RIGHT, Alignment2D::BOTTOM_RIGHT,
text_color, fg_color,
background_color, bg_color,
),
ButtonPos::Middle => icon.draw(
area.center(),
Alignment2D::CENTER,
text_color,
background_color,
), ),
ButtonPos::Middle => {
icon.draw(icon_area.center(), Alignment2D::CENTER, fg_color, bg_color)
}
} }
} }
} }
@ -383,7 +362,7 @@ impl<T> ButtonDetails<T> {
Self { Self {
content: ButtonContent::Icon(icon), content: ButtonContent::Icon(icon),
duration: None, duration: None,
with_outline: true, with_outline: false,
with_arms: false, with_arms: false,
fixed_width: None, fixed_width: None,
offset: Offset::zero(), offset: Offset::zero(),
@ -397,51 +376,42 @@ impl<T> ButtonDetails<T> {
/// Cross-style-icon cancel button with no outline. /// Cross-style-icon cancel button with no outline.
pub fn cancel_icon() -> Self { pub fn cancel_icon() -> Self {
Self::icon(theme::ICON_CANCEL) Self::icon(theme::ICON_CANCEL).with_offset(Offset::new(3, -3))
.with_no_outline()
.with_offset(Offset::new(2, -2))
} }
/// Left arrow to signal going back. No outline. /// Left arrow to signal going back. No outline.
pub fn left_arrow_icon() -> Self { pub fn left_arrow_icon() -> Self {
Self::icon(theme::ICON_ARROW_LEFT) Self::icon(theme::ICON_ARROW_LEFT).with_offset(Offset::new(4, -3))
.with_no_outline()
.with_offset(Offset::new(1, -1))
} }
/// Right arrow to signal going forward. No outline. /// Right arrow to signal going forward. No outline.
pub fn right_arrow_icon() -> Self { pub fn right_arrow_icon() -> Self {
Self::icon(theme::ICON_ARROW_RIGHT) Self::icon(theme::ICON_ARROW_RIGHT).with_offset(Offset::new(-4, -3))
.with_no_outline()
.with_offset(Offset::new(-1, -1))
} }
/// Up arrow to signal paginating back. No outline. Offsetted little right /// Up arrow to signal paginating back. No outline. Offsetted little right
/// to not be on the boundary. /// to not be on the boundary.
pub fn up_arrow_icon() -> Self { pub fn up_arrow_icon() -> Self {
Self::icon(theme::ICON_ARROW_UP) Self::icon(theme::ICON_ARROW_UP).with_offset(Offset::new(2, -3))
.with_no_outline()
.with_offset(Offset::new(2, -3))
} }
/// Down arrow to signal paginating forward. Takes half the screen's width /// Down arrow to signal paginating forward. Takes half the screen's width
pub fn down_arrow_icon_wide() -> Self { pub fn down_arrow_icon_wide() -> Self {
Self::icon(theme::ICON_ARROW_DOWN).with_fixed_width(HALF_SCREEN_BUTTON_WIDTH) Self::icon(theme::ICON_ARROW_DOWN)
.with_outline(true)
.with_fixed_width(HALF_SCREEN_BUTTON_WIDTH)
} }
/// Up arrow to signal paginating back. Takes half the screen's width /// Up arrow to signal paginating back. Takes half the screen's width
pub fn up_arrow_icon_wide() -> Self { pub fn up_arrow_icon_wide() -> Self {
Self::icon(theme::ICON_ARROW_UP).with_fixed_width(HALF_SCREEN_BUTTON_WIDTH) Self::icon(theme::ICON_ARROW_UP)
.with_outline(true)
.with_fixed_width(HALF_SCREEN_BUTTON_WIDTH)
} }
/// Icon of a bin to signal deleting. /// Possible outline around the button.
pub fn bin_icon() -> Self { pub fn with_outline(mut self, outline: bool) -> Self {
Self::icon(theme::ICON_BIN).with_no_outline() self.with_outline = outline;
}
/// No outline around the button.
pub fn with_no_outline(mut self) -> Self {
self.with_outline = false;
self self
} }

View File

@ -106,9 +106,10 @@ pub const CHECKLIST_CURRENT_OFFSET: Offset = Offset::x(3);
// with empty LEFT column and BOTTOM row. // with empty LEFT column and BOTTOM row.
pub const BUTTON_CONTENT_HEIGHT: i16 = 7; pub const BUTTON_CONTENT_HEIGHT: i16 = 7;
pub const BUTTON_OUTLINE: i16 = 3; pub const BUTTON_OUTLINE: i16 = 3;
pub const BUTTON_ARMS: i16 = 2;
pub const BUTTON_HEIGHT: i16 = BUTTON_CONTENT_HEIGHT + 2 * BUTTON_OUTLINE; pub const BUTTON_HEIGHT: i16 = BUTTON_CONTENT_HEIGHT + 2 * BUTTON_OUTLINE;
pub const BUTTON_ICON_WIDTH: i16 = BUTTON_HEIGHT;
pub const TITLE_AREA_HEIGHT: i16 = 12; pub const TITLE_AREA_HEIGHT: i16 = 12;
pub const ARMS_MARGIN: i16 = 2;
// How many pixels should be between text and icons. // How many pixels should be between text and icons.
pub const ELLIPSIS_ICON_MARGIN: i16 = 4; pub const ELLIPSIS_ICON_MARGIN: i16 = 4;