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},
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user