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:
parent
a6316f964e
commit
b420064c08
@ -5,7 +5,7 @@ use crate::{
|
||||
component::{Component, Event, EventCtx, Never},
|
||||
constant,
|
||||
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 {
|
||||
width
|
||||
} else {
|
||||
let outline = if style.with_outline {
|
||||
theme::BUTTON_OUTLINE
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let content_width = match &self.content {
|
||||
ButtonContent::Text(text) => style.font.visible_text_width(text.as_ref()),
|
||||
ButtonContent::Icon(icon) => icon.toif.width(),
|
||||
};
|
||||
content_width + 2 * outline
|
||||
};
|
||||
|
||||
// Button height may be adjusted for the icon without outline
|
||||
// Done to avoid highlighting bigger area than necessary when
|
||||
// drawing the icon in active (black on white) state
|
||||
let button_height = match &self.content {
|
||||
ButtonContent::Text(_) => theme::BUTTON_HEIGHT,
|
||||
ButtonContent::Icon(icon) => {
|
||||
match &self.content {
|
||||
ButtonContent::Text(text) => {
|
||||
let text_width = style.font.visible_text_width(text.as_ref());
|
||||
if style.with_outline {
|
||||
theme::BUTTON_HEIGHT
|
||||
text_width + 2 * theme::BUTTON_OUTLINE
|
||||
} else if style.with_arms {
|
||||
text_width + 2 * theme::ARMS_MARGIN
|
||||
} else {
|
||||
icon.toif.height()
|
||||
text_width
|
||||
}
|
||||
}
|
||||
ButtonContent::Icon(icon) => {
|
||||
// When Icon does not have outline, hardcode its width
|
||||
if style.with_outline {
|
||||
icon.toif.width() + 2 * theme::BUTTON_OUTLINE
|
||||
} else {
|
||||
theme::BUTTON_ICON_WIDTH
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let button_bounds = self.bounds.split_bottom(button_height).1;
|
||||
let area = match self.pos {
|
||||
let button_bounds = self.bounds.split_bottom(theme::BUTTON_HEIGHT).1;
|
||||
match self.pos {
|
||||
ButtonPos::Left => button_bounds.split_left(button_width).0,
|
||||
ButtonPos::Right => button_bounds.split_right(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.
|
||||
fn get_text_baseline(&self, style: &ButtonStyle) -> Point {
|
||||
// Arms and outline require the text to be elevated.
|
||||
let offset_y = if style.with_arms {
|
||||
theme::BUTTON_ARMS
|
||||
} else if style.with_outline {
|
||||
let offset_y = if style.with_outline || style.with_arms {
|
||||
theme::BUTTON_OUTLINE
|
||||
} else {
|
||||
0
|
||||
@ -165,6 +156,8 @@ where
|
||||
|
||||
let offset_x = if style.with_outline {
|
||||
theme::BUTTON_OUTLINE
|
||||
} else if style.with_arms {
|
||||
theme::ARMS_MARGIN
|
||||
} else {
|
||||
0
|
||||
};
|
||||
@ -191,51 +184,41 @@ where
|
||||
|
||||
fn paint(&mut self) {
|
||||
let style = self.style();
|
||||
let text_color = style.text_color;
|
||||
let background_color = text_color.negate();
|
||||
let mut area = self.get_current_area();
|
||||
let fg_color = style.text_color;
|
||||
let bg_color = fg_color.negate();
|
||||
let area = self.get_current_area();
|
||||
let inversed_colors = bg_color != theme::BG;
|
||||
|
||||
// Optionally display "arms" at both sides of content, or create
|
||||
// a nice rounded outline around it.
|
||||
// By default just fill the content background.
|
||||
// Filling the background (with 2-pixel rounding when applicable)
|
||||
if inversed_colors {
|
||||
display::rect_outline_rounded(area, bg_color, fg_color, 2);
|
||||
display::rect_fill(area.shrink(1), bg_color);
|
||||
} else if style.with_outline {
|
||||
display::rect_outline_rounded(area, fg_color, bg_color, 2);
|
||||
} else {
|
||||
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 {
|
||||
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
|
||||
// Putting them one pixel down to touch the bottom.
|
||||
let arms_area = area.translate(Offset::y(1));
|
||||
theme::ICON_ARM_LEFT.draw(
|
||||
area.left_center() - Offset::x(2),
|
||||
arms_area.left_center(),
|
||||
Alignment2D::TOP_RIGHT,
|
||||
text_color,
|
||||
background_color,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
theme::ICON_ARM_RIGHT.draw(
|
||||
area.right_center() + Offset::x(2),
|
||||
arms_area.right_center(),
|
||||
Alignment2D::TOP_LEFT,
|
||||
text_color,
|
||||
background_color,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
} else if style.with_outline {
|
||||
if background_color == theme::BG {
|
||||
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 {
|
||||
display::rect_fill(area, background_color);
|
||||
}
|
||||
|
||||
// Painting the content
|
||||
match &self.content {
|
||||
ButtonContent::Text(text) => {
|
||||
display::text_left(
|
||||
@ -243,37 +226,33 @@ where
|
||||
- Offset::x(style.font.start_x_bearing(text.as_ref())),
|
||||
text.as_ref(),
|
||||
style.font,
|
||||
text_color,
|
||||
background_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
);
|
||||
}
|
||||
ButtonContent::Icon(icon) => {
|
||||
// Allowing for possible offset of the area from current style
|
||||
let icon_area = area.translate(style.offset);
|
||||
if style.with_outline {
|
||||
// Accounting for the 8*8 icon with empty left column and bottom row
|
||||
// (which fits the outline nicely and symmetrically)
|
||||
let center = area.center() + Offset::uniform(1);
|
||||
icon.draw(center, Alignment2D::CENTER, text_color, background_color);
|
||||
icon.draw(icon_area.center(), Alignment2D::CENTER, fg_color, bg_color);
|
||||
} else {
|
||||
// Positioning the icon in the corresponding corner/center
|
||||
match self.pos {
|
||||
ButtonPos::Left => icon.draw(
|
||||
area.bottom_left(),
|
||||
icon_area.bottom_left(),
|
||||
Alignment2D::BOTTOM_LEFT,
|
||||
text_color,
|
||||
background_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
),
|
||||
ButtonPos::Right => icon.draw(
|
||||
area.bottom_right(),
|
||||
icon_area.bottom_right(),
|
||||
Alignment2D::BOTTOM_RIGHT,
|
||||
text_color,
|
||||
background_color,
|
||||
),
|
||||
ButtonPos::Middle => icon.draw(
|
||||
area.center(),
|
||||
Alignment2D::CENTER,
|
||||
text_color,
|
||||
background_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
),
|
||||
ButtonPos::Middle => {
|
||||
icon.draw(icon_area.center(), Alignment2D::CENTER, fg_color, bg_color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -383,7 +362,7 @@ impl<T> ButtonDetails<T> {
|
||||
Self {
|
||||
content: ButtonContent::Icon(icon),
|
||||
duration: None,
|
||||
with_outline: true,
|
||||
with_outline: false,
|
||||
with_arms: false,
|
||||
fixed_width: None,
|
||||
offset: Offset::zero(),
|
||||
@ -397,51 +376,42 @@ impl<T> ButtonDetails<T> {
|
||||
|
||||
/// Cross-style-icon cancel button with no outline.
|
||||
pub fn cancel_icon() -> Self {
|
||||
Self::icon(theme::ICON_CANCEL)
|
||||
.with_no_outline()
|
||||
.with_offset(Offset::new(2, -2))
|
||||
Self::icon(theme::ICON_CANCEL).with_offset(Offset::new(3, -3))
|
||||
}
|
||||
|
||||
/// Left arrow to signal going back. No outline.
|
||||
pub fn left_arrow_icon() -> Self {
|
||||
Self::icon(theme::ICON_ARROW_LEFT)
|
||||
.with_no_outline()
|
||||
.with_offset(Offset::new(1, -1))
|
||||
Self::icon(theme::ICON_ARROW_LEFT).with_offset(Offset::new(4, -3))
|
||||
}
|
||||
|
||||
/// Right arrow to signal going forward. No outline.
|
||||
pub fn right_arrow_icon() -> Self {
|
||||
Self::icon(theme::ICON_ARROW_RIGHT)
|
||||
.with_no_outline()
|
||||
.with_offset(Offset::new(-1, -1))
|
||||
Self::icon(theme::ICON_ARROW_RIGHT).with_offset(Offset::new(-4, -3))
|
||||
}
|
||||
|
||||
/// Up arrow to signal paginating back. No outline. Offsetted little right
|
||||
/// to not be on the boundary.
|
||||
pub fn up_arrow_icon() -> Self {
|
||||
Self::icon(theme::ICON_ARROW_UP)
|
||||
.with_no_outline()
|
||||
.with_offset(Offset::new(2, -3))
|
||||
Self::icon(theme::ICON_ARROW_UP).with_offset(Offset::new(2, -3))
|
||||
}
|
||||
|
||||
/// Down arrow to signal paginating forward. Takes half the screen's width
|
||||
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
|
||||
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.
|
||||
pub fn bin_icon() -> Self {
|
||||
Self::icon(theme::ICON_BIN).with_no_outline()
|
||||
}
|
||||
|
||||
/// No outline around the button.
|
||||
pub fn with_no_outline(mut self) -> Self {
|
||||
self.with_outline = false;
|
||||
/// Possible outline around the button.
|
||||
pub fn with_outline(mut self, outline: bool) -> Self {
|
||||
self.with_outline = outline;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -106,9 +106,10 @@ pub const CHECKLIST_CURRENT_OFFSET: Offset = Offset::x(3);
|
||||
// with empty LEFT column and BOTTOM row.
|
||||
pub const BUTTON_CONTENT_HEIGHT: i16 = 7;
|
||||
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_ICON_WIDTH: i16 = BUTTON_HEIGHT;
|
||||
pub const TITLE_AREA_HEIGHT: i16 = 12;
|
||||
pub const ARMS_MARGIN: i16 = 2;
|
||||
|
||||
// How many pixels should be between text and icons.
|
||||
pub const ELLIPSIS_ICON_MARGIN: i16 = 4;
|
||||
|
Loading…
Reference in New Issue
Block a user