1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-27 10:22:34 +00:00

chore(eckhart): provide short and long vertical menu

This commit is contained in:
Lukas Bielesch 2025-04-25 16:21:36 +02:00 committed by obrusvit
parent 1e653a33cc
commit 00f58bd199
12 changed files with 183 additions and 101 deletions

View File

@ -18,14 +18,14 @@ use crate::{
constant::SCREEN, constant::SCREEN,
firmware::{ firmware::{
Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen, Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
VerticalMenuScreenMsg, MENU_MAX_ITEMS, VerticalMenuScreenMsg, SHORT_MENU_ITEMS,
}, },
}, },
shape::Renderer, shape::Renderer,
}, },
}; };
use super::theme; use super::{theme, ShortMenuVec};
use heapless::Vec; use heapless::Vec;
const MAX_DEPTH: usize = 3; const MAX_DEPTH: usize = 3;
@ -96,11 +96,11 @@ impl MenuItem {
struct Submenu { struct Submenu {
header_text: TString<'static>, header_text: TString<'static>,
show_battery: bool, show_battery: bool,
items: Vec<MenuItem, MENU_MAX_ITEMS>, items: Vec<MenuItem, SHORT_MENU_ITEMS>,
} }
impl Submenu { impl Submenu {
pub fn new(header_text: TString<'static>, items: Vec<MenuItem, MENU_MAX_ITEMS>) -> Self { pub fn new(header_text: TString<'static>, items: Vec<MenuItem, SHORT_MENU_ITEMS>) -> Self {
Self { Self {
header_text, header_text,
show_battery: false, show_battery: false,
@ -140,7 +140,7 @@ pub struct DeviceMenuScreen<'a> {
// as defined by `enum Subscreen` (DeviceScreen is still a VerticalMenuScreen!) // as defined by `enum Subscreen` (DeviceScreen is still a VerticalMenuScreen!)
// The active one will be Some(...) and the other one will be None. // The active one will be Some(...) and the other one will be None.
// This way we only need to keep one screen at any time in memory. // This way we only need to keep one screen at any time in memory.
menu_screen: GcBox<Option<VerticalMenuScreen>>, menu_screen: GcBox<Option<VerticalMenuScreen<ShortMenuVec>>>,
about_screen: GcBox<Option<TextScreen<Paragraphs<[Paragraph<'a>; 2]>>>>, about_screen: GcBox<Option<TextScreen<Paragraphs<[Paragraph<'a>; 2]>>>>,
// Information needed to construct any subscreen on demand // Information needed to construct any subscreen on demand
@ -209,7 +209,7 @@ impl<'a> DeviceMenuScreen<'a> {
paired_devices: Vec<TString<'static>, 1>, paired_devices: Vec<TString<'static>, 1>,
paired_device_indices: Vec<usize, 1>, paired_device_indices: Vec<usize, 1>,
) -> usize { ) -> usize {
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new(); let mut items: Vec<MenuItem, SHORT_MENU_ITEMS> = Vec::new();
for (device, idx) in paired_devices.iter().zip(paired_device_indices) { for (device, idx) in paired_devices.iter().zip(paired_device_indices) {
unwrap!(items.push( unwrap!(items.push(
MenuItem::new(*device, Some(Action::GoTo(idx))).with_subtext(Some(( MenuItem::new(*device, Some(Action::GoTo(idx))).with_subtext(Some((
@ -224,7 +224,7 @@ impl<'a> DeviceMenuScreen<'a> {
} }
fn add_pair_and_connect_menu(&mut self, manage_devices_index: usize) -> usize { fn add_pair_and_connect_menu(&mut self, manage_devices_index: usize) -> usize {
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new(); let mut items: Vec<MenuItem, SHORT_MENU_ITEMS> = Vec::new();
unwrap!(items.push( unwrap!(items.push(
MenuItem::new( MenuItem::new(
"Manage paired devices".into(), "Manage paired devices".into(),
@ -245,7 +245,7 @@ impl<'a> DeviceMenuScreen<'a> {
} }
fn add_settings_menu(&mut self, security_index: usize, device_index: usize) -> usize { fn add_settings_menu(&mut self, security_index: usize, device_index: usize) -> usize {
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new(); let mut items: Vec<MenuItem, SHORT_MENU_ITEMS> = Vec::new();
unwrap!(items.push(MenuItem::new( unwrap!(items.push(MenuItem::new(
"Security".into(), "Security".into(),
Some(Action::GoTo(security_index)) Some(Action::GoTo(security_index))
@ -260,7 +260,7 @@ impl<'a> DeviceMenuScreen<'a> {
} }
fn add_security_menu(&mut self) -> usize { fn add_security_menu(&mut self) -> usize {
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new(); let mut items: Vec<MenuItem, SHORT_MENU_ITEMS> = Vec::new();
unwrap!(items.push(MenuItem::new( unwrap!(items.push(MenuItem::new(
"Check backup".into(), "Check backup".into(),
Some(Action::Return(DeviceMenuMsg::CheckBackup)), Some(Action::Return(DeviceMenuMsg::CheckBackup)),
@ -275,7 +275,7 @@ impl<'a> DeviceMenuScreen<'a> {
} }
fn add_device_menu(&mut self, device_name: TString<'static>, about_index: usize) -> usize { fn add_device_menu(&mut self, device_name: TString<'static>, about_index: usize) -> usize {
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new(); let mut items: Vec<MenuItem, SHORT_MENU_ITEMS> = Vec::new();
unwrap!( unwrap!(
items.push(MenuItem::new("Name".into(), None).with_subtext(Some((device_name, None)))) items.push(MenuItem::new("Name".into(), None).with_subtext(Some((device_name, None))))
); );
@ -298,7 +298,7 @@ impl<'a> DeviceMenuScreen<'a> {
pair_and_connect_index: usize, pair_and_connect_index: usize,
settings_index: usize, settings_index: usize,
) -> usize { ) -> usize {
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new(); let mut items: Vec<MenuItem, SHORT_MENU_ITEMS> = Vec::new();
if failed_backup { if failed_backup {
unwrap!(items.push( unwrap!(items.push(
MenuItem::new( MenuItem::new(
@ -349,7 +349,7 @@ impl<'a> DeviceMenuScreen<'a> {
Subscreen::Submenu(ref mut submenu_index) => { Subscreen::Submenu(ref mut submenu_index) => {
let submenu = &self.submenus[*submenu_index]; let submenu = &self.submenus[*submenu_index];
*self.about_screen.deref_mut() = None; *self.about_screen.deref_mut() = None;
let mut menu = VerticalMenu::empty().with_separators(); let mut menu = VerticalMenu::<ShortMenuVec>::empty().with_separators();
for item in &submenu.items { for item in &submenu.items {
let button = if let Some((subtext, subtext_style)) = item.subtext { let button = if let Some((subtext, subtext_style)) = item.subtext {
Button::new_menu_item_with_subtext( Button::new_menu_item_with_subtext(
@ -361,7 +361,7 @@ impl<'a> DeviceMenuScreen<'a> {
} else { } else {
Button::new_menu_item(item.text, item.stylesheet) Button::new_menu_item(item.text, item.stylesheet)
}; };
menu = menu.item(button); menu.item(button);
} }
let mut header = Header::new(submenu.header_text).with_close_button(); let mut header = Header::new(submenu.header_text).with_close_button();
if submenu.show_battery { if submenu.show_battery {
@ -385,8 +385,8 @@ impl<'a> DeviceMenuScreen<'a> {
Subscreen::DeviceScreen(device, _) => { Subscreen::DeviceScreen(device, _) => {
*self.about_screen.deref_mut() = None; *self.about_screen.deref_mut() = None;
let mut menu = VerticalMenu::empty().with_separators(); let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(Button::new_menu_item(device, theme::menu_item_title())); menu.item(Button::new_menu_item(device, theme::menu_item_title()));
menu = menu.item(Button::new_menu_item( menu.item(Button::new_menu_item(
"Disconnect".into(), "Disconnect".into(),
theme::menu_item_title_red(), theme::menu_item_title_red(),
)); ));

View File

@ -40,7 +40,10 @@ pub use qr_screen::{QrMsg, QrScreen};
pub use select_word_screen::{SelectWordMsg, SelectWordScreen}; pub use select_word_screen::{SelectWordMsg, SelectWordScreen};
pub use share_words::{ShareWordsScreen, ShareWordsScreenMsg}; pub use share_words::{ShareWordsScreen, ShareWordsScreenMsg};
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg}; pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
pub use vertical_menu::{VerticalMenu, VerticalMenuMsg, MENU_MAX_ITEMS}; pub use vertical_menu::{
LongMenuGc, MenuItems, ShortMenuVec, VerticalMenu, VerticalMenuMsg, LONG_MENU_ITEMS,
SHORT_MENU_ITEMS,
};
pub use vertical_menu_screen::{VerticalMenuScreen, VerticalMenuScreenMsg}; pub use vertical_menu_screen::{VerticalMenuScreen, VerticalMenuScreenMsg};
use super::{constant, theme}; use super::{constant, theme};

View File

@ -1,9 +1,14 @@
use crate::ui::{ use core::ops::DerefMut;
use crate::{
micropython::gc::GcBox,
ui::{
component::{Component, Event, EventCtx}, component::{Component, Event, EventCtx},
event::TouchEvent, event::TouchEvent,
geometry::{Direction, Insets, Offset, Rect}, geometry::{Direction, Insets, Offset, Rect},
shape::{Bar, Renderer}, shape::{Bar, Renderer},
util::animation_disabled, util::animation_disabled,
},
}; };
use super::{ use super::{
@ -14,15 +19,78 @@ use heapless::Vec;
/// Number of buttons. /// Number of buttons.
/// Presently, VerticalMenu holds only fixed number of buttons. /// Presently, VerticalMenu holds only fixed number of buttons.
pub const MENU_MAX_ITEMS: usize = 5; pub const LONG_MENU_ITEMS: usize = 100;
pub const SHORT_MENU_ITEMS: usize = 5;
type VerticalMenuButtons = Vec<Button, MENU_MAX_ITEMS>; pub type LongMenuGc = GcBox<Vec<Button, LONG_MENU_ITEMS>>;
pub type ShortMenuVec = Vec<Button, SHORT_MENU_ITEMS>;
pub struct VerticalMenu { pub trait MenuItems: Default {
fn empty() -> Self {
Self::default()
}
fn push(&mut self, button: Button);
fn iter(&self) -> core::slice::Iter<'_, Button>;
fn iter_mut(&mut self) -> core::slice::IterMut<'_, Button>;
fn get_len(&self) -> usize;
fn get_last(&self) -> Option<&Button>;
}
impl MenuItems for ShortMenuVec {
fn push(&mut self, button: Button) {
unwrap!(self.push(button));
}
fn iter(&self) -> core::slice::Iter<'_, Button> {
self.as_slice().iter()
}
fn iter_mut(&mut self) -> core::slice::IterMut<'_, Button> {
self.as_mut_slice().iter_mut()
}
fn get_len(&self) -> usize {
self.len()
}
fn get_last(&self) -> Option<&Button> {
self.last()
}
}
impl Default for GcBox<Vec<Button, LONG_MENU_ITEMS>> {
fn default() -> Self {
unwrap!(GcBox::new(Vec::new()))
}
}
impl MenuItems for LongMenuGc {
fn push(&mut self, button: Button) {
unwrap!(self.deref_mut().push(button));
}
fn iter(&self) -> core::slice::Iter<'_, Button> {
self.as_slice().iter()
}
fn iter_mut(&mut self) -> core::slice::IterMut<'_, Button> {
self.as_mut_slice().iter_mut()
}
fn get_len(&self) -> usize {
self.len()
}
fn get_last(&self) -> Option<&Button> {
self.last()
}
}
pub struct VerticalMenu<T = ShortMenuVec> {
/// Bounds the sliding window of the menu. /// Bounds the sliding window of the menu.
bounds: Rect, bounds: Rect,
/// Menu items. /// Menu items.
buttons: VerticalMenuButtons, buttons: T,
/// Full height of the menu, including overflowing items. /// Full height of the menu, including overflowing items.
total_height: i16, total_height: i16,
/// Vertical offset of the current view. /// Vertical offset of the current view.
@ -37,12 +105,12 @@ pub enum VerticalMenuMsg {
Selected(usize), Selected(usize),
} }
impl VerticalMenu { impl<T: MenuItems> VerticalMenu<T> {
const SIDE_INSETS: Insets = Insets::sides(12); const SIDE_INSETS: Insets = Insets::sides(12);
const DEFAULT_PADDING: i16 = 28; const DEFAULT_PADDING: i16 = 28;
const MIN_PADDING: i16 = 2; const MIN_PADDING: i16 = 2;
fn new(buttons: VerticalMenuButtons) -> Self { fn new(buttons: T) -> Self {
Self { Self {
bounds: Rect::zero(), bounds: Rect::zero(),
buttons, buttons,
@ -54,7 +122,7 @@ impl VerticalMenu {
} }
pub fn empty() -> Self { pub fn empty() -> Self {
Self::new(VerticalMenuButtons::new()) Self::new(T::default())
} }
pub fn with_separators(mut self) -> Self { pub fn with_separators(mut self) -> Self {
@ -62,8 +130,13 @@ impl VerticalMenu {
self self
} }
pub fn item(mut self, button: Button) -> Self { pub fn with_item(mut self, button: Button) -> Self {
unwrap!(self.buttons.push(button)); self.buttons.push(button);
self
}
pub fn item(&mut self, button: Button) -> &mut Self {
self.buttons.push(button);
self self
} }
@ -112,7 +185,7 @@ impl VerticalMenu {
debug_assert!(dir == Direction::Up || dir == Direction::Down); debug_assert!(dir == Direction::Up || dir == Direction::Down);
// For single button, the menu is not scrollable // For single button, the menu is not scrollable
if self.buttons.len() < 2 { if self.buttons.get_len() < 2 {
return; return;
} }
@ -120,7 +193,11 @@ impl VerticalMenu {
let current = self.offset_y; let current = self.offset_y;
let mut cumsum = 0; let mut cumsum = 0;
for button in &self.buttons[..self.buttons.len() - 1] { for button in self
.buttons
.iter()
.take(self.buttons.get_len().saturating_sub(1))
{
let new_cumsum = cumsum + button.area().height(); let new_cumsum = cumsum + button.area().height();
match dir { match dir {
Direction::Up if new_cumsum > current => { Direction::Up if new_cumsum > current => {
@ -146,7 +223,7 @@ impl VerticalMenu {
self.offset_y_max = self.total_height self.offset_y_max = self.total_height
- self - self
.buttons .buttons
.last() .get_last()
.unwrap_or(&Button::empty()) .unwrap_or(&Button::empty())
.area() .area()
.height(); .height();
@ -158,7 +235,7 @@ impl VerticalMenu {
// Find the first button from the top that would completely fit in the menu area // Find the first button from the top that would completely fit in the menu area
// in the bottom position // in the bottom position
for button in &self.buttons { for button in self.buttons.iter() {
let offset = button.area().top_left().y - self.bounds.top_left().y; let offset = button.area().top_left().y - self.bounds.top_left().y;
if offset > menu_overflow { if offset > menu_overflow {
self.offset_y_max = offset; self.offset_y_max = offset;
@ -186,15 +263,16 @@ impl VerticalMenu {
} }
fn render_buttons<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render_buttons<'s>(&'s self, target: &mut impl Renderer<'s>) {
for button in &self.buttons { for button in self.buttons.iter() {
button.render(target); button.render(target);
} }
} }
fn render_separators<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render_separators<'s>(&'s self, target: &mut impl Renderer<'s>) {
for i in 1..self.buttons.len() { for pair in self.buttons.iter().as_slice().windows(2) {
let button = &self.buttons[i]; let [button_prev, button] = pair else {
let button_prev = &self.buttons[i - 1]; continue;
};
if !button.is_pressed() && !button_prev.is_pressed() { if !button.is_pressed() && !button_prev.is_pressed() {
let separator = Rect::from_top_left_and_size( let separator = Rect::from_top_left_and_size(
@ -212,7 +290,7 @@ impl VerticalMenu {
} }
} }
impl Component for VerticalMenu { impl<T: MenuItems> Component for VerticalMenu<T> {
type Msg = VerticalMenuMsg; type Msg = VerticalMenuMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -269,11 +347,14 @@ impl Component for VerticalMenu {
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for VerticalMenu { impl<T: MenuItems> crate::trace::Trace for VerticalMenu<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
// Trace the VerticalMenu component
t.component("VerticalMenu"); t.component("VerticalMenu");
// Trace the buttons as a list
t.in_list("buttons", &|button_list| { t.in_list("buttons", &|button_list| {
for button in &self.buttons { for button in self.buttons.iter() {
button_list.child(button); button_list.child(button);
} }
}); });

View File

@ -13,12 +13,12 @@ use crate::{
}, },
}; };
use super::{constant::SCREEN, theme, Header, HeaderMsg, VerticalMenu, VerticalMenuMsg}; use super::{constant::SCREEN, theme, Header, HeaderMsg, MenuItems, VerticalMenu, VerticalMenuMsg};
pub struct VerticalMenuScreen { pub struct VerticalMenuScreen<T> {
header: Header, header: Header,
/// Scrollable vertical menu /// Scrollable vertical menu
menu: VerticalMenu, menu: VerticalMenu<T>,
/// Base position of the menu sliding window to scroll around /// Base position of the menu sliding window to scroll around
offset_base: i16, offset_base: i16,
/// Swipe detector /// Swipe detector
@ -37,9 +37,9 @@ pub enum VerticalMenuScreenMsg {
Menu, Menu,
} }
impl VerticalMenuScreen { impl<T: MenuItems> VerticalMenuScreen<T> {
const TOUCH_SENSITIVITY_DIVIDER: i16 = 15; const TOUCH_SENSITIVITY_DIVIDER: i16 = 15;
pub fn new(menu: VerticalMenu) -> Self { pub fn new(menu: VerticalMenu<T>) -> Self {
Self { Self {
header: Header::new(TString::empty()), header: Header::new(TString::empty()),
menu, menu,
@ -164,7 +164,7 @@ impl VerticalMenuScreen {
} }
} }
impl Component for VerticalMenuScreen { impl<T: MenuItems> Component for VerticalMenuScreen<T> {
type Msg = VerticalMenuScreenMsg; type Msg = VerticalMenuScreenMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -211,7 +211,7 @@ impl Component for VerticalMenuScreen {
} }
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
impl Swipable for VerticalMenuScreen { impl<T: MenuItems> Swipable for VerticalMenuScreen<T> {
fn get_swipe_config(&self) -> SwipeConfig { fn get_swipe_config(&self) -> SwipeConfig {
self.swipe_config self.swipe_config
} }
@ -222,7 +222,7 @@ impl Swipable for VerticalMenuScreen {
} }
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for VerticalMenuScreen { impl<T: MenuItems> crate::trace::Trace for VerticalMenuScreen<T> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("VerticalMenuScreen"); t.component("VerticalMenuScreen");
t.child("Header", &self.header); t.child("Header", &self.header);

View File

@ -19,8 +19,8 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, FidoCredential, Header, TextScreen, TextScreenMsg, VerticalMenu, ActionBar, FidoCredential, Header, LongMenuGc, ShortMenuVec, TextScreen, TextScreenMsg,
VerticalMenuScreen, VerticalMenuScreenMsg, VerticalMenu, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
}; };
@ -111,13 +111,13 @@ pub fn new_confirm_fido(
}); });
// Choose credential screen // Choose credential screen
let mut credentials = VerticalMenu::empty(); let mut credentials = VerticalMenu::<LongMenuGc>::empty();
for i in 0..num_accounts { for i in 0..num_accounts {
let account = unwrap!(accounts.get(i)); let account = unwrap!(accounts.get(i));
let label = account let label = account
.try_into() .try_into()
.unwrap_or_else(|_| TString::from_str("-")); .unwrap_or_else(|_| TString::from_str("-"));
credentials = credentials.item(Button::new_menu_item(label, theme::menu_item_title())); credentials.item(Button::new_menu_item(label, theme::menu_item_title()));
} }
let content_choose_credential = VerticalMenuScreen::new(credentials) let content_choose_credential = VerticalMenuScreen::new(credentials)
.with_header(Header::new(TR::fido__title_select_credential.into())) .with_header(Header::new(TR::fido__title_select_credential.into()))
@ -158,10 +158,9 @@ pub fn new_confirm_fido(
}); });
// Menu screen // Menu screen
let content_menu = VerticalMenuScreen::new(VerticalMenu::empty().item(Button::new_menu_item( let content_menu = VerticalMenuScreen::new(VerticalMenu::<ShortMenuVec>::empty().with_item(
TR::buttons__cancel.into(), Button::new_menu_item(TR::buttons__cancel.into(), theme::menu_item_title_orange()),
theme::menu_item_title_orange(), ))
)))
.with_header(Header::new(title).with_close_button()) .with_header(Header::new(title).with_close_button())
.map(|msg| match msg { .map(|msg| match msg {
VerticalMenuScreenMsg::Selected(0) => Some(FlowMsg::Choice(0)), VerticalMenuScreenMsg::Selected(0) => Some(FlowMsg::Choice(0)),

View File

@ -22,8 +22,8 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, Header, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen, ActionBar, Header, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreenMsg, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
}; };
@ -213,11 +213,11 @@ fn content_main_menu(
address_params: bool, address_params: bool,
account_params: bool, account_params: bool,
cancel_menu_label: TString<'static>, cancel_menu_label: TString<'static>,
) -> MsgMap<VerticalMenuScreen, impl Fn(VerticalMenuScreenMsg) -> Option<FlowMsg>> { ) -> MsgMap<VerticalMenuScreen<ShortMenuVec>, impl Fn(VerticalMenuScreenMsg) -> Option<FlowMsg>> {
let mut main_menu = VerticalMenu::empty(); let mut main_menu = VerticalMenu::<ShortMenuVec>::empty();
let mut main_menu_items = Vec::<usize, 3>::new(); let mut main_menu_items = Vec::<usize, 3>::new();
if address_params { if address_params {
main_menu = main_menu.item( main_menu.item(
Button::with_text(address_title) Button::with_text(address_title)
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start), .with_text_align(Alignment::Start),
@ -225,7 +225,7 @@ fn content_main_menu(
unwrap!(main_menu_items.push(MENU_ITEM_ADDRESS_INFO)); unwrap!(main_menu_items.push(MENU_ITEM_ADDRESS_INFO));
} }
if account_params { if account_params {
main_menu = main_menu.item( main_menu.item(
Button::with_text(TR::address_details__account_info.into()) Button::with_text(TR::address_details__account_info.into())
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
@ -233,7 +233,7 @@ fn content_main_menu(
); );
unwrap!(main_menu_items.push(MENU_ITEM_ACCOUNT_INFO)); unwrap!(main_menu_items.push(MENU_ITEM_ACCOUNT_INFO));
} }
main_menu = main_menu.item( main_menu.item(
Button::with_text(cancel_menu_label) Button::with_text(cancel_menu_label)
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
@ -241,7 +241,7 @@ fn content_main_menu(
); );
unwrap!(main_menu_items.push(MENU_ITEM_CANCEL)); unwrap!(main_menu_items.push(MENU_ITEM_CANCEL));
VerticalMenuScreen::new(main_menu) VerticalMenuScreen::<ShortMenuVec>::new(main_menu)
.with_header(Header::new(TString::empty()).with_close_button()) .with_header(Header::new(TString::empty()).with_close_button())
.map(move |msg| match msg { .map(move |msg| match msg {
VerticalMenuScreenMsg::Selected(i) => { VerticalMenuScreenMsg::Selected(i) => {
@ -434,18 +434,17 @@ pub fn new_confirm_output(
.with_pages(|_| 1); .with_pages(|_| 1);
// SummaryMenu // SummaryMenu
let mut summary_menu = VerticalMenu::empty(); let mut summary_menu = VerticalMenu::<ShortMenuVec>::empty();
let mut summary_menu_items = Vec::<usize, 3>::new(); let mut summary_menu_items = Vec::<usize, 3>::new();
if account_menu_item { if account_menu_item {
summary_menu = summary_menu.item(Button::with_text(account_title)); summary_menu.item(Button::with_text(account_title));
unwrap!(summary_menu_items.push(MENU_ITEM_EXTRA_INFO)); unwrap!(summary_menu_items.push(MENU_ITEM_EXTRA_INFO));
} }
if fee_menu_item { if fee_menu_item {
summary_menu =
summary_menu.item(Button::with_text(TR::confirm_total__title_fee.into())); summary_menu.item(Button::with_text(TR::confirm_total__title_fee.into()));
unwrap!(summary_menu_items.push(MENU_ITEM_FEE_INFO)); unwrap!(summary_menu_items.push(MENU_ITEM_FEE_INFO));
} }
summary_menu = summary_menu summary_menu
.item(Button::with_text(cancel_menu_label).styled(theme::menu_item_title_orange())); .item(Button::with_text(cancel_menu_label).styled(theme::menu_item_title_orange()));
unwrap!(summary_menu_items.push(MENU_ITEM_CANCEL)); unwrap!(summary_menu_items.push(MENU_ITEM_CANCEL));
let content_summary_menu = VerticalMenuScreen::new(summary_menu) let content_summary_menu = VerticalMenuScreen::new(summary_menu)

View File

@ -9,7 +9,7 @@ use crate::{
FlowController, FlowMsg, SwipeFlow, FlowController, FlowMsg, SwipeFlow,
}, },
geometry::{Alignment, Direction, Offset}, geometry::{Alignment, Direction, Offset},
layout_eckhart::component::Button, layout_eckhart::{component::Button, firmware::ShortMenuVec},
}, },
}; };
@ -84,7 +84,7 @@ pub fn new_confirm_reset(recovery: bool) -> Result<SwipeFlow, error::Error> {
.one_button_request(br); .one_button_request(br);
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty().item( VerticalMenu::<ShortMenuVec>::empty().with_item(
Button::with_text(TR::buttons__cancel.into()) Button::with_text(TR::buttons__cancel.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)

View File

@ -20,8 +20,8 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, Header, Hint, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen, ActionBar, Header, Hint, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreenMsg, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
}; };
@ -131,11 +131,11 @@ pub fn new_confirm_summary(
}); });
// Menu // Menu
let mut menu = VerticalMenu::empty(); let mut menu = VerticalMenu::<ShortMenuVec>::empty();
let mut menu_items = Vec::<usize, 3>::new(); let mut menu_items = Vec::<usize, 3>::new();
if account_paragraphs.is_some() { if account_paragraphs.is_some() {
menu = menu.item( menu.item(
Button::with_text(TR::address_details__account_info.into()) Button::with_text(TR::address_details__account_info.into())
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
@ -144,7 +144,7 @@ pub fn new_confirm_summary(
unwrap!(menu_items.push(MENU_ITEM_ACCOUNT_INFO)); unwrap!(menu_items.push(MENU_ITEM_ACCOUNT_INFO));
} }
if extra_paragraphs.is_some() { if extra_paragraphs.is_some() {
menu = menu.item( menu.item(
Button::with_text(extra_title.unwrap_or(TR::buttons__more_info.into())) Button::with_text(extra_title.unwrap_or(TR::buttons__more_info.into()))
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
@ -152,7 +152,7 @@ pub fn new_confirm_summary(
); );
unwrap!(menu_items.push(MENU_ITEM_EXTRA_INFO)); unwrap!(menu_items.push(MENU_ITEM_EXTRA_INFO));
} }
menu = menu.item( menu.item(
Button::with_text(verb_cancel.unwrap_or(TR::buttons__cancel.into())) Button::with_text(verb_cancel.unwrap_or(TR::buttons__cancel.into()))
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)

View File

@ -23,8 +23,8 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, Header, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen, ActionBar, Header, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreenMsg, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
}; };
@ -208,7 +208,7 @@ pub fn new_continue_recovery_homepage(
let res = if show_instructions { let res = if show_instructions {
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty().item( VerticalMenu::<ShortMenuVec>::empty().with_item(
Button::with_text(cancel_btn.into()) Button::with_text(cancel_btn.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
@ -228,8 +228,8 @@ pub fn new_continue_recovery_homepage(
res res
} else if pages.is_none() { } else if pages.is_none() {
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty() VerticalMenu::<ShortMenuVec>::empty()
.item( .with_item(
Button::with_text_and_subtext( Button::with_text_and_subtext(
TR::words__recovery_share.into(), TR::words__recovery_share.into(),
TR::buttons__more_info.into(), TR::buttons__more_info.into(),
@ -239,7 +239,7 @@ pub fn new_continue_recovery_homepage(
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)), .with_content_offset(Offset::x(12)),
) )
.item( .with_item(
Button::with_text(cancel_btn.into()) Button::with_text(cancel_btn.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
@ -280,14 +280,14 @@ pub fn new_continue_recovery_homepage(
res res
} else { } else {
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty() VerticalMenu::<ShortMenuVec>::empty()
.item( .with_item(
Button::with_text(TR::recovery__title_remaining_shares.into()) Button::with_text(TR::recovery__title_remaining_shares.into())
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)), .with_content_offset(Offset::x(12)),
) )
.item( .with_item(
Button::with_text(cancel_btn.into()) Button::with_text(cancel_btn.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)

View File

@ -20,8 +20,8 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, Header, HeaderMsg, Hint, QrScreen, TextScreen, TextScreenMsg, VerticalMenu, ActionBar, Header, HeaderMsg, Hint, QrScreen, ShortMenuVec, TextScreen, TextScreenMsg,
VerticalMenuScreen, VerticalMenuScreenMsg, VerticalMenu, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
}; };
@ -132,20 +132,20 @@ pub fn new_get_address(
// Menu // Menu
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty() VerticalMenu::<ShortMenuVec>::empty()
.item( .with_item(
Button::with_text(TR::address__qr_code.into()) Button::with_text(TR::address__qr_code.into())
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)), .with_content_offset(Offset::x(12)),
) )
.item( .with_item(
Button::with_text(TR::address_details__account_info.into()) Button::with_text(TR::address_details__account_info.into())
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)), .with_content_offset(Offset::x(12)),
) )
.item( .with_item(
Button::with_text(TR::buttons__cancel.into()) Button::with_text(TR::buttons__cancel.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)

View File

@ -18,7 +18,7 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, Header, HeaderMsg, Hint, TextScreen, TextScreenMsg, VerticalMenu, ActionBar, Header, HeaderMsg, Hint, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreen, VerticalMenuScreenMsg, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
@ -73,7 +73,7 @@ pub fn new_prompt_backup() -> Result<SwipeFlow, error::Error> {
}); });
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty().item( VerticalMenu::<ShortMenuVec>::empty().with_item(
Button::with_text(TR::backup__title_skip.into()) Button::with_text(TR::backup__title_skip.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)

View File

@ -18,8 +18,8 @@ use crate::{
use super::super::{ use super::super::{
component::Button, component::Button,
firmware::{ firmware::{
ActionBar, Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen, ActionBar, Header, HeaderMsg, ShortMenuVec, TextScreen, TextScreenMsg, VerticalMenu,
VerticalMenuScreenMsg, VerticalMenuScreen, VerticalMenuScreenMsg,
}, },
theme, theme,
}; };
@ -89,15 +89,15 @@ pub fn new_show_danger(
// Menu // Menu
let content_menu = VerticalMenuScreen::new( let content_menu = VerticalMenuScreen::new(
VerticalMenu::empty() VerticalMenu::<ShortMenuVec>::empty()
.with_separators() .with_separators()
.item( .with_item(
Button::with_text(verb_cancel) Button::with_text(verb_cancel)
.styled(theme::menu_item_title()) .styled(theme::menu_item_title())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)
.with_content_offset(Offset::x(12)), .with_content_offset(Offset::x(12)),
) )
.item( .with_item(
Button::with_text(TR::words__continue_anyway.into()) Button::with_text(TR::words__continue_anyway.into())
.styled(theme::menu_item_title_orange()) .styled(theme::menu_item_title_orange())
.with_text_align(Alignment::Start) .with_text_align(Alignment::Start)