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:
parent
f1a3f289bf
commit
38548f02f2
BIN
core/assets/back_btn.png
Normal file
BIN
core/assets/back_btn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
BIN
core/assets/back_btn_tall.png
Normal file
BIN
core/assets/back_btn_tall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
@ -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 {
|
||||||
|
@ -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(
|
||||||
|
@ -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},
|
||||||
|
@ -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)]
|
||||||
|
@ -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),
|
||||||
|
@ -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.
|
||||||
|
@ -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());
|
||||||
|
BIN
core/embed/rust/src/ui/model_tt/res/back_btn.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/back_btn.toif
Normal file
Binary file not shown.
BIN
core/embed/rust/src/ui/model_tt/res/back_btn_tall.toif
Normal file
BIN
core/embed/rust/src/ui/model_tt/res/back_btn_tall.toif
Normal file
Binary file not shown.
@ -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,
|
||||||
|
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user