Skip to content

Address several DQA cases for Eckhart #5590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/embed/rust/librust_qstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ static void _librust_qstrs(void) {
MP_QSTR_details_title;
MP_QSTR_device_name;
MP_QSTR_device_name__change_template;
MP_QSTR_device_name__changed;
MP_QSTR_device_name__continue_with_empty_label;
MP_QSTR_device_name__enter;
MP_QSTR_device_name__title;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ impl<'a> DeviceMenuScreen<'a> {
match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut submenu_index) => {
let submenu = &self.submenus[*submenu_index];
let mut menu = VerticalMenu::<ShortMenuVec>::empty().with_separators();
let mut menu = VerticalMenu::<ShortMenuVec>::empty();
for item in &submenu.items {
let button = if let Some((subtext, subtext_style)) = item.subtext {
let subtext_style =
Expand Down Expand Up @@ -413,7 +413,7 @@ impl<'a> DeviceMenuScreen<'a> {
ActiveScreen::Menu(VerticalMenuScreen::new(menu).with_header(header));
}
Subscreen::DeviceScreen(device, _) => {
let mut menu = VerticalMenu::empty().with_separators();
let mut menu = VerticalMenu::empty();
menu.item(Button::new_menu_item(device, theme::menu_item_title()));
menu.item(Button::new_menu_item(
"Disconnect".into(),
Expand Down
73 changes: 42 additions & 31 deletions core/embed/rust/src/ui/layout_eckhart/firmware/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@ use super::{
constant, theme,
};

const BUTTON_EXPAND_BORDER: i16 = 32;

/// Component for the header of a screen. Eckhart UI shows the title (can be two
/// lines), optional icon button on the left, and optional icon button
/// (typically for menu) on the right.
pub struct Header {
area: Rect,
title: Label<'static>,
/// button in the top-right corner
right_button: Option<Button>,
Expand All @@ -30,6 +27,7 @@ pub struct Header {
/// icon in the top-left corner (used instead of left button)
icon: Option<Icon>,
icon_color: Option<Color>,
icon_area: Rect,
/// Battery status indicator
fuel_gauge: Option<FuelGauge>,
}
Expand All @@ -43,19 +41,18 @@ pub enum HeaderMsg {

impl Header {
pub const HEADER_HEIGHT: i16 = theme::HEADER_HEIGHT; // [px]
pub const HEADER_BUTTON_WIDTH: i16 = 56; // [px]
pub const HEADER_INSETS: Insets = Insets::sides(24); // [px]
const BUTTON_TOUCH_EXPAND: Insets = Insets::sides(32); // [px]

pub const fn new(title: TString<'static>) -> Self {
Self {
area: Rect::zero(),
title: Label::left_aligned(title, theme::label_title_main()).vertically_centered(),
right_button: None,
left_button: None,
right_button_msg: HeaderMsg::Cancelled,
left_button_msg: HeaderMsg::Cancelled,
icon: None,
icon_color: None,
icon_area: Rect::zero(),
fuel_gauge: Some(FuelGauge::on_charging_change()),
}
}
Expand All @@ -69,9 +66,12 @@ impl Header {
#[inline(never)]
pub fn with_right_button(self, button: Button, msg: HeaderMsg) -> Self {
debug_assert!(matches!(button.content(), ButtonContent::Icon(_)));
let touch_area = Insets::uniform(BUTTON_EXPAND_BORDER);
Self {
right_button: Some(button.with_expanded_touch_area(touch_area)),
right_button: Some(
button
.with_expanded_touch_area(Self::BUTTON_TOUCH_EXPAND)
.with_radius(12),
),
right_button_msg: msg,
..self
}
Expand All @@ -80,10 +80,13 @@ impl Header {
#[inline(never)]
pub fn with_left_button(self, button: Button, msg: HeaderMsg) -> Self {
debug_assert!(matches!(button.content(), ButtonContent::Icon(_)));
let touch_area = Insets::uniform(BUTTON_EXPAND_BORDER);
Self {
icon: None,
left_button: Some(button.with_expanded_touch_area(touch_area)),
left_button: Some(
button
.with_expanded_touch_area(Self::BUTTON_TOUCH_EXPAND)
.with_radius(12),
),
left_button_msg: msg,
..self
}
Expand Down Expand Up @@ -126,20 +129,35 @@ impl Header {
ctx.request_paint();
}

/// Calculates the width needed for the right button
fn right_button_width(&self) -> i16 {
if let Some(b) = &self.right_button {
match b.content() {
ButtonContent::Icon(icon) => icon.toif.width() + 2 * theme::PADDING,
// We do not expect any other ButtonContent as the right button of the Header
_ => unreachable!(),
}
} else {
// Title must have a default padding from the right side
theme::PADDING
}
}

/// Calculates the width needed for the left icon, be it a button with icon
/// or just icon
fn left_icon_width(&self) -> i16 {
let margin_right: i16 = 16; // [px]
fn left_object_width(&self) -> i16 {
const ICON_MARGIN_RIGHT: i16 = 16; // [px]
if let Some(b) = &self.left_button {
match b.content() {
ButtonContent::Icon(icon) => icon.toif.width() + margin_right,
ButtonContent::Icon(icon) => icon.toif.width() + 2 * theme::PADDING,
// We do not expect any other ButtonContent as the left button of the Header
_ => unreachable!(),
}
} else if let Some(icon) = self.icon {
icon.toif.width() + margin_right
theme::PADDING + icon.toif.width() + ICON_MARGIN_RIGHT
} else {
0
// Title must have a default padding from the left side
theme::PADDING
}
}
}
Expand All @@ -148,31 +166,24 @@ impl Component for Header {
type Msg = HeaderMsg;

fn place(&mut self, bounds: Rect) -> Rect {
debug_assert_eq!(bounds.width(), constant::screen().width());
debug_assert_eq!(bounds.width(), constant::SCREEN.width());
debug_assert_eq!(bounds.height(), Self::HEADER_HEIGHT);

let bounds = bounds.inset(Self::HEADER_INSETS);
let rest = if let Some(b) = &mut self.right_button {
let (rest, right_button_area) = bounds.split_right(Self::HEADER_BUTTON_WIDTH);
b.place(right_button_area);
rest
} else {
bounds
};

let icon_width = self.left_icon_width();
let (left_button_area, title_area) = rest.split_left(icon_width);
let (rest, right_button_area) = bounds.split_right(self.right_button_width());
self.icon_area = rest.inset(Insets::left(theme::PADDING));
let (left_object_area, title_area) = rest.split_left(self.left_object_width());

self.left_button.place(left_button_area);
self.left_button.place(left_object_area);
self.title.place(title_area);
self.fuel_gauge.place(title_area.union(left_button_area));
self.fuel_gauge.place(self.icon_area);
self.right_button.place(right_button_area);

if let Some(fuel_gauge) = &mut self.fuel_gauge {
// Force update the fuel gauge state, so it is up-to-date
// necessary e.g. for the first DeviceMenu Submenu re-entry
fuel_gauge.update_pm_state();
}

self.area = bounds;
bounds
}

Expand Down Expand Up @@ -200,7 +211,7 @@ impl Component for Header {
} else {
self.left_button.render(target);
if let Some(icon) = self.icon {
shape::ToifImage::new(self.area.left_center(), icon.toif)
shape::ToifImage::new(self.icon_area.left_center(), icon.toif)
.with_fg(self.icon_color.unwrap_or(theme::GREY_LIGHT))
.with_align(Alignment2D::CENTER_LEFT)
.render(target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl HoldToConfirmAnim {
// FIXME: vert_center is precisely aligned with the `Header` title (which uses
// `Label`) but this solution might break with `Header` changes
let text_offset = Offset::new(
Header::HEADER_INSETS.left,
theme::PADDING,
font.vert_center(0, Header::HEADER_HEIGHT - 1, "A"),
);
let text_pos = header_pad.top_left() + text_offset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ pub struct VerticalMenu<T = ShortMenuVec> {
offset_y: i16,
/// Maximum vertical offset.
offset_y_max: i16,
/// Whether to show separators between buttons.
separators: bool,
}

pub enum VerticalMenuMsg {
Expand All @@ -116,19 +114,13 @@ impl<T: MenuItems> VerticalMenu<T> {
total_height: 0,
offset_y: 0,
offset_y_max: 0,
separators: false,
}
}

pub fn empty() -> Self {
Self::new(T::default())
}

pub fn with_separators(mut self) -> Self {
self.separators = true;
self
}

pub fn with_item(mut self, button: Button) -> Self {
self.buttons.push(button);
self
Expand Down Expand Up @@ -337,9 +329,7 @@ impl<T: MenuItems> Component for VerticalMenu<T> {
self.render_buttons(target);

// Render separators between buttons
if self.separators {
self.render_separators(target);
}
self.render_separators(target);
});
});
}
Expand Down
7 changes: 2 additions & 5 deletions core/embed/rust/src/ui/layout_eckhart/flow/confirm_reset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, Hint, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
ActionBar, Header, Hint, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme::{self, gradient::Gradient},
Expand Down Expand Up @@ -90,10 +90,7 @@ pub fn new_confirm_reset(recovery: bool) -> Result<SwipeFlow, error::Error> {
let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
))
.with_header(
Header::new(title)
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.with_header(Header::new(title).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
Expand Down
7 changes: 2 additions & 5 deletions core/embed/rust/src/ui/layout_eckhart/flow/prompt_backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, Hint, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
ActionBar, Header, Hint, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme::{self, gradient::Gradient},
Expand Down Expand Up @@ -79,10 +79,7 @@ pub fn new_prompt_backup() -> Result<SwipeFlow, error::Error> {
theme::menu_item_title_orange(),
),
))
.with_header(
Header::new(title)
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.with_header(Header::new(title).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
Expand Down
14 changes: 4 additions & 10 deletions core/embed/rust/src/ui/layout_eckhart/flow/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use heapless::Vec;
use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, Hint, QrScreen, ShortMenuVec, TextScreen, TextScreenMsg,
VerticalMenu, VerticalMenuScreen, VerticalMenuScreenMsg,
ActionBar, Header, Hint, QrScreen, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme::{self, gradient::Gradient},
};
Expand Down Expand Up @@ -159,10 +159,7 @@ pub fn new_receive(
theme::menu_item_title_orange(),
)),
)
.with_header(
Header::new(title)
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.with_header(Header::new(title).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
Expand Down Expand Up @@ -214,10 +211,7 @@ pub fn new_receive(
para.into_paragraphs()
.with_placement(LinearPlacement::vertical()),
)
.with_header(
Header::new(TR::address_details__account_info.into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.with_header(Header::new(TR::address_details__account_info.into()).with_close_button())
.map(|_| Some(FlowMsg::Cancelled));

// Cancel
Expand Down
8 changes: 2 additions & 6 deletions core/embed/rust/src/ui/layout_eckhart/flow/show_danger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
use super::super::{
component::Button,
firmware::{
ActionBar, Header, HeaderMsg, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
ActionBar, Header, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg,
},
theme,
Expand Down Expand Up @@ -92,17 +92,13 @@ pub fn new_show_danger(
// Menu
let content_menu = VerticalMenuScreen::new(
VerticalMenu::<ShortMenuVec>::empty()
.with_separators()
.with_item(Button::new_menu_item(verb_cancel, theme::menu_item_title()))
.with_item(Button::new_menu_item(
TR::words__continue_anyway.into(),
theme::menu_item_title_orange(),
)),
)
.with_header(
Header::new(menu_title.unwrap_or("".into()))
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
)
.with_header(Header::new(menu_title.unwrap_or(TString::empty())).with_close_button())
.map(|msg| match msg {
VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)),
VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled),
Expand Down
12 changes: 7 additions & 5 deletions core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,12 @@ impl FirmwareUI for UIEckhart {
}
}
let text = FormattedText::new(ops);
let action_bar = ActionBar::new_double(
Button::with_icon(theme::ICON_CROSS),
Button::with_text(verb.unwrap_or(TR::buttons__confirm.into())),
);
let right_button = if let Some(verb) = verb {
Button::with_text(verb)
} else {
Button::with_text(TR::buttons__confirm.into()).styled(button_confirm())
};
let action_bar = ActionBar::new_double(Button::with_icon(theme::ICON_CROSS), right_button);
let screen = TextScreen::new(text)
.with_header(Header::new(title))
.with_action_bar(action_bar);
Expand Down Expand Up @@ -984,7 +986,7 @@ impl FirmwareUI for UIEckhart {
_current: usize,
cancel: Option<TString<'static>>,
) -> Result<impl LayoutMaybeTrace, Error> {
let mut menu = VerticalMenu::<ShortMenuVec>::empty().with_separators();
let mut menu = VerticalMenu::<ShortMenuVec>::empty();
for text in &items {
menu.item(Button::new_menu_item(*text, theme::menu_item_title()));
}
Expand Down
Loading