1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-24 23:38:09 +00:00

feat(core/rust): special shape erase button

[no changelog]
This commit is contained in:
tychovrahe 2022-11-28 23:21:15 +01:00 committed by TychoVrahe
parent f1a3f289bf
commit 38548f02f2
13 changed files with 108 additions and 64 deletions

BIN
core/assets/back_btn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -79,7 +79,6 @@ impl BlendedImage {
} }
} }
#[cfg(feature = "dma2d")]
fn paint_image(&self) { fn paint_image(&self) {
display::icon_over_icon( display::icon_over_icon(
None, None,
@ -88,17 +87,6 @@ impl BlendedImage {
self.area_color, self.area_color,
); );
} }
#[cfg(not(feature = "dma2d"))]
fn paint_image(&self) {
display::icon_top_left(self.bg_top_left, self.bg, self.bg_color, self.area_color);
display::icon_top_left(
self.bg_top_left + self.fg_offset,
self.fg,
self.fg_color,
self.bg_color,
);
}
} }
impl Component for BlendedImage { impl Component for BlendedImage {

View File

@ -748,6 +748,27 @@ pub fn icon_over_icon(
dma2d_wait_for_transfer(); dma2d_wait_for_transfer();
} }
#[cfg(not(feature = "dma2d"))]
pub fn icon_over_icon(
bg_area: Option<Rect>,
bg: (&[u8], Offset, Color),
fg: (&[u8], Offset, Color),
bg_color: Color,
) {
let (data_bg, offset_bg, color_icon_bg) = bg;
let (data_fg, offset_fg, color_icon_fg) = fg;
let pos_bg = if let Some(area) = bg_area {
rect_fill(area, bg_color);
area.top_left() + offset_bg
} else {
Point::from(offset_bg)
};
icon_top_left(pos_bg, data_bg, color_icon_bg, bg_color);
icon_top_left(pos_bg + offset_fg, data_fg, color_icon_fg, color_icon_bg);
}
/// Gets a color of a pixel on `p` coordinates of rounded rectangle with corner /// Gets a color of a pixel on `p` coordinates of rounded rectangle with corner
/// radius 2 /// radius 2
fn rect_rounded2_get_pixel( fn rect_rounded2_get_pixel(

View File

@ -6,7 +6,6 @@ use crate::{
Child, Component, ComponentExt, Event, EventCtx, Label, Pad, Child, Component, ComponentExt, Event, EventCtx, Label, Pad,
}, },
constant::screen, constant::screen,
display::Font,
geometry::{Alignment, Insets, LinearPlacement, Point, Rect}, geometry::{Alignment, Insets, LinearPlacement, Point, Rect},
model_tr::{ model_tr::{
component::{Button, ButtonMsg, ButtonPos, ResultAnim, ResultAnimMsg}, component::{Button, ButtonMsg, ButtonPos, ResultAnim, ResultAnimMsg},

View File

@ -52,6 +52,10 @@ impl<T> Button<T> {
Self::new(ButtonContent::Icon(image)) Self::new(ButtonContent::Icon(image))
} }
pub fn with_icon_blend(bg: &'static [u8], fg: &'static [u8], fg_offset: Offset) -> Self {
Self::new(ButtonContent::IconBlend(bg, fg, fg_offset))
}
pub fn empty() -> Self { pub fn empty() -> Self {
Self::new(ButtonContent::Empty) Self::new(ButtonContent::Empty)
} }
@ -141,29 +145,34 @@ impl<T> Button<T> {
} }
pub fn paint_background(&self, style: &ButtonStyle) { pub fn paint_background(&self, style: &ButtonStyle) {
if style.border_width > 0 { match &self.content {
// Paint the border and a smaller background on top of it. ButtonContent::IconBlend(_, _, _) => {}
display::rect_fill_rounded( _ => {
self.area, if style.border_width > 0 {
style.border_color, // Paint the border and a smaller background on top of it.
style.background_color, display::rect_fill_rounded(
style.border_radius, self.area,
); style.border_color,
display::rect_fill_rounded( style.background_color,
self.area.inset(Insets::uniform(style.border_width)), style.border_radius,
style.button_color, );
style.border_color, display::rect_fill_rounded(
style.border_radius, self.area.inset(Insets::uniform(style.border_width)),
); style.button_color,
} else { style.border_color,
// We do not need to draw an explicit border in this case, just a style.border_radius,
// bigger background. );
display::rect_fill_rounded( } else {
self.area, // We do not need to draw an explicit border in this case, just a
style.button_color, // bigger background.
style.background_color, display::rect_fill_rounded(
style.border_radius, self.area,
); style.button_color,
style.background_color,
style.border_radius,
);
}
}
} }
} }
@ -196,6 +205,12 @@ impl<T> Button<T> {
style.button_color, style.button_color,
); );
} }
ButtonContent::IconBlend(bg, fg, offset) => display::icon_over_icon(
Some(self.area),
(bg, Offset::zero(), style.button_color),
(fg, *offset, style.text_color),
style.background_color,
),
} }
} }
} }
@ -300,6 +315,7 @@ where
ButtonContent::Empty => {} ButtonContent::Empty => {}
ButtonContent::Text(text) => t.field("text", text), ButtonContent::Text(text) => t.field("text", text),
ButtonContent::Icon(_) => t.symbol("icon"), ButtonContent::Icon(_) => t.symbol("icon"),
ButtonContent::IconBlend(_, _, _) => t.symbol("icon"),
} }
t.close(); t.close();
} }
@ -318,6 +334,7 @@ pub enum ButtonContent<T> {
Empty, Empty,
Text(T), Text(T),
Icon(&'static [u8]), Icon(&'static [u8]),
IconBlend(&'static [u8], &'static [u8], Offset),
} }
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]

View File

@ -2,7 +2,7 @@ use core::ops::Deref;
use crate::ui::{ use crate::ui::{
component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe}, component::{maybe::paint_overlapping, Child, Component, Event, EventCtx, Label, Maybe},
geometry::{Alignment, Grid, Rect}, geometry::{Alignment, Grid, Offset, Rect},
model_tt::{ model_tt::{
component::{Button, ButtonMsg}, component::{Button, ButtonMsg},
theme, theme,
@ -39,7 +39,12 @@ where
)), )),
back: Child::new(Maybe::hidden( back: Child::new(Maybe::hidden(
theme::BG, theme::BG,
Button::with_icon(theme::ICON_BACK).styled(theme::button_clear()), Button::with_icon_blend(
theme::IMAGE_BG_BACK_BTN_TALL,
theme::ICON_BACK,
Offset::new(30, 17),
)
.styled(theme::button_clear()),
)), )),
input: Child::new(Maybe::hidden(theme::BG, input)), input: Child::new(Maybe::hidden(theme::BG, input)),
keys: T::keys().map(Button::with_text).map(Child::new), keys: T::keys().map(Button::with_text).map(Child::new),

View File

@ -4,7 +4,10 @@ use crate::ui::{
geometry::{Grid, Insets, Offset, Rect}, geometry::{Grid, Insets, Offset, Rect},
model_tt::component::{ model_tt::component::{
button::{Button, ButtonContent, ButtonMsg::Clicked}, button::{Button, ButtonContent, ButtonMsg::Clicked},
keyboard::common::{paint_pending_marker, MultiTapKeyboard, TextBox, HEADER_PADDING_SIDE}, keyboard::common::{
paint_pending_marker, MultiTapKeyboard, TextBox, HEADER_HEIGHT, HEADER_PADDING_BOTTOM,
HEADER_PADDING_SIDE,
},
swipe::{Swipe, SwipeDirection}, swipe::{Swipe, SwipeDirection},
theme, ScrollBar, theme, ScrollBar,
}, },
@ -46,10 +49,14 @@ impl PassphraseKeyboard {
confirm: Button::with_icon(theme::ICON_CONFIRM) confirm: Button::with_icon(theme::ICON_CONFIRM)
.styled(theme::button_confirm()) .styled(theme::button_confirm())
.into_child(), .into_child(),
back: Button::with_icon(theme::ICON_BACK) back: Button::with_icon_blend(
.styled(theme::button_reset()) theme::IMAGE_BG_BACK_BTN,
.initially_enabled(false) theme::ICON_BACK,
.into_child(), Offset::new(30, 12),
)
.styled(theme::button_reset())
.initially_enabled(false)
.into_child(),
keys: KEYBOARD.map(|page| { keys: KEYBOARD.map(|page| {
page.map(|text| { page.map(|text| {
if text == " " { if text == " " {
@ -70,6 +77,7 @@ impl PassphraseKeyboard {
ButtonContent::Text(text) => text, ButtonContent::Text(text) => text,
ButtonContent::Icon(_) => " ", ButtonContent::Icon(_) => " ",
ButtonContent::Empty => "", ButtonContent::Empty => "",
ButtonContent::IconBlend(_, _, _) => "",
} }
} }
@ -135,17 +143,16 @@ impl Component for PassphraseKeyboard {
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let bounds = bounds.inset(theme::borders()); let bounds = bounds.inset(theme::borders());
let input_area = Grid::new(bounds, 5, 1) let (input_area, key_grid_area) = bounds.split_top(HEADER_HEIGHT + HEADER_PADDING_BOTTOM);
.with_spacing(theme::KEYBOARD_SPACING)
.row_col(0, 0);
let (input_area, scroll_area) = input_area.split_bottom(ScrollBar::DOT_SIZE); let (input_area, scroll_area) =
let input_area = input_area.split_bottom(ScrollBar::DOT_SIZE + theme::KEYBOARD_SPACING);
input_area.inset(Insets::new(0, HEADER_PADDING_SIDE, 2, HEADER_PADDING_SIDE)); let (scroll_area, _) = scroll_area.split_top(ScrollBar::DOT_SIZE);
let input_area = input_area.inset(Insets::sides(HEADER_PADDING_SIDE));
let key_grid = Grid::new(bounds, 5, 3).with_spacing(theme::KEYBOARD_SPACING); let key_grid = Grid::new(key_grid_area, 4, 3).with_spacing(theme::KEYBOARD_SPACING);
let confirm_btn_area = key_grid.cell(14); let confirm_btn_area = key_grid.cell(11);
let back_btn_area = key_grid.cell(12); let back_btn_area = key_grid.cell(9);
self.page_swipe.place(bounds); self.page_swipe.place(bounds);
self.input.place(input_area); self.input.place(input_area);
@ -162,10 +169,10 @@ impl Component for PassphraseKeyboard {
// from the second row. // from the second row.
let area = key_grid.cell(if key < 9 { let area = key_grid.cell(if key < 9 {
// The grid has 3 columns, and we skip the first row. // The grid has 3 columns, and we skip the first row.
key + 3 key
} else { } else {
// For the last key (the "0" position) we skip one cell. // For the last key (the "0" position) we skip one cell.
key + 1 + 3 key + 1
}); });
btn.place(area); btn.place(area);
} }
@ -280,10 +287,11 @@ impl Component for Input {
} }
fn paint(&mut self) { fn paint(&mut self) {
const TEXT_OFFSET: Offset = Offset::y(8);
let style = theme::label_default(); let style = theme::label_default();
let mut text_baseline = self.area.bottom_left() - TEXT_OFFSET;
let mut text_baseline = self.area.top_left() + Offset::y(style.text_font.text_height())
- Offset::y(style.text_font.text_baseline());
let text = self.textbox.content(); let text = self.textbox.content();
// Preparing the new text to be displayed. // Preparing the new text to be displayed.

View File

@ -70,10 +70,14 @@ where
allow_cancel: bool, allow_cancel: bool,
) -> Self { ) -> Self {
// Control buttons. // Control buttons.
let erase_btn = Button::with_icon(theme::ICON_BACK) let erase_btn = Button::with_icon_blend(
.styled(theme::button_reset()) theme::IMAGE_BG_BACK_BTN,
.with_long_press(ERASE_HOLD_DURATION) theme::ICON_BACK,
.initially_enabled(false); Offset::new(30, 12),
)
.styled(theme::button_reset())
.with_long_press(ERASE_HOLD_DURATION)
.initially_enabled(false);
let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child(); let erase_btn = Maybe::hidden(theme::BG, erase_btn).into_child();
let cancel_btn = Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel()); let cancel_btn = Button::with_icon(theme::ICON_CANCEL).styled(theme::button_cancel());

Binary file not shown.

Binary file not shown.

View File

@ -65,6 +65,8 @@ pub const IMAGE_FG_ERROR: &[u8] = include_res!("model_tt/res/error_fg.toif");
pub const IMAGE_FG_INFO: &[u8] = include_res!("model_tt/res/info_fg.toif"); pub const IMAGE_FG_INFO: &[u8] = include_res!("model_tt/res/info_fg.toif");
pub const IMAGE_BG_CIRCLE: &[u8] = include_res!("model_tt/res/circle.toif"); pub const IMAGE_BG_CIRCLE: &[u8] = include_res!("model_tt/res/circle.toif");
pub const IMAGE_BG_TRIANGLE: &[u8] = include_res!("model_tt/res/triangle.toif"); pub const IMAGE_BG_TRIANGLE: &[u8] = include_res!("model_tt/res/triangle.toif");
pub const IMAGE_BG_BACK_BTN: &[u8] = include_res!("model_tt/res/back_btn.toif");
pub const IMAGE_BG_BACK_BTN_TALL: &[u8] = include_res!("model_tt/res/back_btn_tall.toif");
// Scrollbar/PIN dots. // Scrollbar/PIN dots.
pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif"); pub const DOT_ACTIVE: &[u8] = include_res!("model_tt/res/scroll-active.toif");
@ -229,7 +231,7 @@ pub fn button_reset() -> ButtonStyleSheet {
}, },
disabled: &ButtonStyle { disabled: &ButtonStyle {
font: Font::BOLD, font: Font::BOLD,
text_color: GREY_LIGHT, text_color: FG,
button_color: YELLOW, button_color: YELLOW,
background_color: BG, background_color: BG,
border_color: BG, border_color: BG,

View File

@ -1646,10 +1646,10 @@
"TT_test_session_id_and_passphrase.py::test_multiple_passphrases": "7f73c410413a655054ab7fcc95d9da528bd082a89cda3ee981c4c48b566e6660", "TT_test_session_id_and_passphrase.py::test_multiple_passphrases": "7f73c410413a655054ab7fcc95d9da528bd082a89cda3ee981c4c48b566e6660",
"TT_test_session_id_and_passphrase.py::test_multiple_sessions": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1", "TT_test_session_id_and_passphrase.py::test_multiple_sessions": "f03b50df7f4a161078fa903c44f37272961b70358d4014d30a12888e1fd2caf1",
"TT_test_session_id_and_passphrase.py::test_passphrase_ack_mismatch": "95a40f79fa7ffceb10e89b513c203b4937112b8d764cdba3c1df538355dc129c", "TT_test_session_id_and_passphrase.py::test_passphrase_ack_mismatch": "95a40f79fa7ffceb10e89b513c203b4937112b8d764cdba3c1df538355dc129c",
"TT_test_session_id_and_passphrase.py::test_passphrase_always_on_device": "ea3049463b15861f70fab2d4a56fc5efd75b6e6464b9f81e19478e9f8ef48f8e", "TT_test_session_id_and_passphrase.py::test_passphrase_always_on_device": "d2302e826a90ac2aaec7f7bcf9794fe1ffa077f04d9566ce4edbd53975caee16",
"TT_test_session_id_and_passphrase.py::test_passphrase_length": "928b70f715015cb291bcd9890190dc53a4a31a7965d7b7eadfa5ec2e7a3a47a1", "TT_test_session_id_and_passphrase.py::test_passphrase_length": "928b70f715015cb291bcd9890190dc53a4a31a7965d7b7eadfa5ec2e7a3a47a1",
"TT_test_session_id_and_passphrase.py::test_passphrase_missing": "95a40f79fa7ffceb10e89b513c203b4937112b8d764cdba3c1df538355dc129c", "TT_test_session_id_and_passphrase.py::test_passphrase_missing": "95a40f79fa7ffceb10e89b513c203b4937112b8d764cdba3c1df538355dc129c",
"TT_test_session_id_and_passphrase.py::test_passphrase_on_device": "9e9111f802d024580996acb984e520d68b0b61efaec50173f8755770586fd7bd", "TT_test_session_id_and_passphrase.py::test_passphrase_on_device": "3425b0aa0fb34c5fb40dadd63533701ba93b752b9218d8cce72cc9c9e24c0236",
"TT_test_session_id_and_passphrase.py::test_session_enable_passphrase": "9c96dc98e60e7b3a16d7f328f92a001e8c6727902e6912100fc45b3dabee612c", "TT_test_session_id_and_passphrase.py::test_session_enable_passphrase": "9c96dc98e60e7b3a16d7f328f92a001e8c6727902e6912100fc45b3dabee612c",
"TT_test_session_id_and_passphrase.py::test_session_with_passphrase": "a7046b2ffe82d6b5194ad34d17d0fa2e16bfa29ab8df798dc38d2b40de01c434", "TT_test_session_id_and_passphrase.py::test_session_with_passphrase": "a7046b2ffe82d6b5194ad34d17d0fa2e16bfa29ab8df798dc38d2b40de01c434",
"TT_tezos-test_getaddress.py::test_tezos_get_address": "d5c6a3fcb7bf58d62be0eb6d3d51fa17ebeadfb8ec3359f11e5b3771bb33e4f4", "TT_tezos-test_getaddress.py::test_tezos_get_address": "d5c6a3fcb7bf58d62be0eb6d3d51fa17ebeadfb8ec3359f11e5b3771bb33e4f4",