1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-20 15:08:46 +00:00

fix(eckhart): optimize device menu

This commit is contained in:
Ioan Bizău 2025-04-08 13:58:17 +02:00 committed by obrusvit
parent 1029fe684c
commit 07424f1625
2 changed files with 75 additions and 88 deletions

View File

@ -1,4 +1,8 @@
use core::ops::DerefMut;
use crate::{ use crate::{
error::Error,
micropython::gc::GcBox,
strutil::TString, strutil::TString,
ui::{ ui::{
component::{ component::{
@ -24,8 +28,9 @@ use crate::{
use super::theme; use super::theme;
use heapless::Vec; use heapless::Vec;
const MAX_DEPTH: usize = 5; const MAX_DEPTH: usize = 3;
const MAX_SUBSCREENS: usize = 10; const MAX_SUBSCREENS: usize = 8;
const MAX_SUBMENUS: usize = MAX_SUBSCREENS - 2 /* (about and device screen) */;
const DISCONNECT_DEVICE_MENU_INDEX: usize = 1; const DISCONNECT_DEVICE_MENU_INDEX: usize = 1;
@ -88,13 +93,13 @@ impl MenuItem {
} }
} }
struct SubmenuScreen { 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, MENU_MAX_ITEMS>,
} }
impl SubmenuScreen { 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, MENU_MAX_ITEMS>) -> Self {
Self { Self {
header_text, header_text,
@ -110,10 +115,9 @@ impl SubmenuScreen {
} }
// Each subscreen of the DeviceMenuScreen is one of these // Each subscreen of the DeviceMenuScreen is one of these
#[allow(clippy::large_enum_variant)]
enum Subscreen { enum Subscreen {
// A menu, with associated items and actions // A registered submenu
Submenu(SubmenuScreen), Submenu(usize),
// A screen allowing the user to to disconnect a device // A screen allowing the user to to disconnect a device
DeviceScreen( DeviceScreen(
@ -133,14 +137,14 @@ pub struct DeviceMenuScreen<'a> {
// These correspond to the currently active subscreen, // These correspond to the currently active subscreen,
// which is one of the possible kinds of subscreens // which is one of the possible kinds of subscreens
// as defined by `enum Subscreen` // as defined by `enum Subscreen` (DeviceScreen is still a VerticalMenuScreen!)
// The active one will be Some(...) and the other two 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: Option<VerticalMenuScreen>, menu_screen: GcBox<Option<VerticalMenuScreen>>,
paired_device_screen: Option<VerticalMenuScreen>, about_screen: GcBox<Option<TextScreen<Paragraphs<[Paragraph<'a>; 2]>>>>,
about_screen: Option<TextScreen<Paragraphs<[Paragraph<'a>; 2]>>>,
// Information needed to construct any subscreen on demand // Information needed to construct any subscreen on demand
submenus: GcBox<Vec<Submenu, MAX_SUBMENUS>>,
subscreens: Vec<Subscreen, MAX_SUBSCREENS>, subscreens: Vec<Subscreen, MAX_SUBSCREENS>,
// index of the current subscreen in the list of subscreens // index of the current subscreen in the list of subscreens
@ -162,15 +166,15 @@ impl<'a> DeviceMenuScreen<'a> {
// (see component_msg_obj.rs, which currently just returns "DeviceDisconnect" with no // (see component_msg_obj.rs, which currently just returns "DeviceDisconnect" with no
// index!) // index!)
paired_devices: Vec<TString<'static>, 1>, paired_devices: Vec<TString<'static>, 1>,
) -> Self { ) -> Result<Self, Error> {
let mut screen = Self { let mut screen = Self {
bounds: Rect::zero(), bounds: Rect::zero(),
battery_percentage, battery_percentage,
firmware_version, firmware_version,
menu_screen: None, menu_screen: GcBox::new(None)?,
paired_device_screen: None, about_screen: GcBox::new(None)?,
about_screen: None,
active_subscreen: 0, active_subscreen: 0,
submenus: GcBox::new(Vec::new())?,
subscreens: Vec::new(), subscreens: Vec::new(),
parent_subscreens: Vec::new(), parent_subscreens: Vec::new(),
}; };
@ -193,7 +197,7 @@ impl<'a> DeviceMenuScreen<'a> {
screen.set_active_subscreen(root); screen.set_active_subscreen(root);
screen Ok(screen)
} }
fn is_low_battery(&self) -> bool { fn is_low_battery(&self) -> bool {
@ -215,10 +219,8 @@ impl<'a> DeviceMenuScreen<'a> {
)); ));
} }
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new( let submenu_index = self.add_submenu(Submenu::new("Manage paired devices".into(), items));
"Manage paired devices".into(), self.add_subscreen(Subscreen::Submenu(submenu_index))
items,
)))
} }
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 {
@ -238,10 +240,8 @@ impl<'a> DeviceMenuScreen<'a> {
Some(Action::Return(DeviceMenuMsg::DevicePair)), Some(Action::Return(DeviceMenuMsg::DevicePair)),
))); )));
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new( let submenu_index = self.add_submenu(Submenu::new("Pair & connect".into(), items));
"Pair & connect".into(), self.add_subscreen(Subscreen::Submenu(submenu_index))
items,
)))
} }
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 {
@ -255,10 +255,8 @@ impl<'a> DeviceMenuScreen<'a> {
Some(Action::GoTo(device_index)) Some(Action::GoTo(device_index))
))); )));
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new( let submenu_index = self.add_submenu(Submenu::new("Settings".into(), items));
"Settings".into(), self.add_subscreen(Subscreen::Submenu(submenu_index))
items,
)))
} }
fn add_security_menu(&mut self) -> usize { fn add_security_menu(&mut self) -> usize {
@ -272,10 +270,8 @@ impl<'a> DeviceMenuScreen<'a> {
Some(Action::Return(DeviceMenuMsg::WipeDevice)) Some(Action::Return(DeviceMenuMsg::WipeDevice))
))); )));
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new( let submenu_index = self.add_submenu(Submenu::new("Security".into(), items));
"Security".into(), self.add_subscreen(Subscreen::Submenu(submenu_index))
items,
)))
} }
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 {
@ -292,10 +288,8 @@ impl<'a> DeviceMenuScreen<'a> {
Some(Action::GoTo(about_index)) Some(Action::GoTo(about_index))
))); )));
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new( let submenu_index = self.add_submenu(Submenu::new("Device".into(), items));
"Device".into(), self.add_subscreen(Subscreen::Submenu(submenu_index))
items,
)))
} }
fn add_root_menu( fn add_root_menu(
@ -329,9 +323,14 @@ impl<'a> DeviceMenuScreen<'a> {
"Settings".into(), "Settings".into(),
Some(Action::GoTo(settings_index)), Some(Action::GoTo(settings_index)),
))); )));
self.add_subscreen(Subscreen::Submenu(
SubmenuScreen::new("".into(), items).with_battery(), let submenu_index = self.add_submenu(Submenu::new("".into(), items).with_battery());
)) self.add_subscreen(Subscreen::Submenu(submenu_index))
}
fn add_submenu(&mut self, submenu: Submenu) -> usize {
unwrap!(self.submenus.push(submenu));
self.submenus.len() - 1
} }
fn add_subscreen(&mut self, screen: Subscreen) -> usize { fn add_subscreen(&mut self, screen: Subscreen) -> usize {
@ -347,9 +346,9 @@ impl<'a> DeviceMenuScreen<'a> {
fn build_active_subscreen(&mut self) { fn build_active_subscreen(&mut self) {
match self.subscreens[self.active_subscreen] { match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut submenu) => { Subscreen::Submenu(ref mut submenu_index) => {
self.paired_device_screen = None; let submenu = &self.submenus[*submenu_index];
self.about_screen = None; *self.about_screen.deref_mut() = None;
let mut menu = VerticalMenu::empty().with_separators(); let mut menu = VerticalMenu::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 {
@ -364,8 +363,7 @@ impl<'a> DeviceMenuScreen<'a> {
}; };
menu = menu.item(button); menu = menu.item(button);
} }
let mut header = Header::new(submenu.header_text) let mut header = Header::new(submenu.header_text).with_close_button();
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled);
if submenu.show_battery { if submenu.show_battery {
header = header.with_icon( header = header.with_icon(
theme::ICON_BATTERY_ZAP, theme::ICON_BATTERY_ZAP,
@ -381,24 +379,21 @@ impl<'a> DeviceMenuScreen<'a> {
HeaderMsg::Back, HeaderMsg::Back,
); );
} }
self.menu_screen = Some(VerticalMenuScreen::new(menu).with_header(header)); *self.menu_screen.deref_mut() =
Some(VerticalMenuScreen::new(menu).with_header(header));
} }
Subscreen::DeviceScreen(device, _) => { Subscreen::DeviceScreen(device, _) => {
self.menu_screen = None; *self.about_screen.deref_mut() = None;
self.about_screen = 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 = menu.item(Button::new_menu_item(device, theme::menu_item_title()));
menu = menu.item(Button::new_menu_item( menu = menu.item(Button::new_menu_item(
"Disconnect".into(), "Disconnect".into(),
theme::menu_item_title_red(), theme::menu_item_title_red(),
)); ));
self.paired_device_screen = Some( *self.menu_screen.deref_mut() = Some(
VerticalMenuScreen::new(menu).with_header( VerticalMenuScreen::new(menu).with_header(
Header::new("Manage".into()) Header::new("Manage".into())
.with_right_button( .with_close_button()
Button::with_icon(theme::ICON_CROSS),
HeaderMsg::Cancelled,
)
.with_left_button( .with_left_button(
Button::with_icon(theme::ICON_CHEVRON_LEFT), Button::with_icon(theme::ICON_CHEVRON_LEFT),
HeaderMsg::Back, HeaderMsg::Back,
@ -407,14 +402,13 @@ impl<'a> DeviceMenuScreen<'a> {
); );
} }
Subscreen::AboutScreen => { Subscreen::AboutScreen => {
self.menu_screen = None; *self.menu_screen.deref_mut() = None;
self.paired_device_screen = None;
let about_content = Paragraphs::new([ let about_content = Paragraphs::new([
Paragraph::new(&theme::firmware::TEXT_REGULAR, "Firmware version"), Paragraph::new(&theme::firmware::TEXT_REGULAR, "Firmware version"),
Paragraph::new(&theme::firmware::TEXT_REGULAR, self.firmware_version), Paragraph::new(&theme::firmware::TEXT_REGULAR, self.firmware_version),
]); ]);
self.about_screen = Some( *self.about_screen.deref_mut() = Some(
TextScreen::new(about_content) TextScreen::new(about_content)
.with_header(Header::new("About".into()).with_close_button()), .with_header(Header::new("About".into()).with_close_button()),
); );
@ -424,8 +418,8 @@ impl<'a> DeviceMenuScreen<'a> {
fn handle_submenu(&mut self, ctx: &mut EventCtx, idx: usize) -> Option<DeviceMenuMsg> { fn handle_submenu(&mut self, ctx: &mut EventCtx, idx: usize) -> Option<DeviceMenuMsg> {
match self.subscreens[self.active_subscreen] { match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut menu_screen) => { Subscreen::Submenu(ref mut submenu_index) => {
match menu_screen.items[idx].action { match self.submenus[*submenu_index].items[idx].action {
Some(Action::GoTo(menu)) => { Some(Action::GoTo(menu)) => {
self.menu_screen.as_mut().unwrap().update_menu(ctx); self.menu_screen.as_mut().unwrap().update_menu(ctx);
unwrap!(self.parent_subscreens.push(self.active_subscreen)); unwrap!(self.parent_subscreens.push(self.active_subscreen));
@ -466,8 +460,7 @@ impl<'a> Component for DeviceMenuScreen<'a> {
self.bounds = bounds; self.bounds = bounds;
match self.subscreens[self.active_subscreen] { match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(..) => self.menu_screen.place(bounds), Subscreen::Submenu(..) | Subscreen::DeviceScreen(..) => self.menu_screen.place(bounds),
Subscreen::DeviceScreen(..) => self.paired_device_screen.place(bounds),
Subscreen::AboutScreen => self.about_screen.place(bounds), Subscreen::AboutScreen => self.about_screen.place(bounds),
}; };
@ -476,33 +469,28 @@ impl<'a> Component for DeviceMenuScreen<'a> {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
// Handle the event for the active menu // Handle the event for the active menu
match self.subscreens[self.active_subscreen] { let subscreen = &self.subscreens[self.active_subscreen];
Subscreen::Submenu(..) => match self.menu_screen.event(ctx, event) { match subscreen {
Some(VerticalMenuScreenMsg::Selected(index)) => { Subscreen::Submenu(..) | Subscreen::DeviceScreen(..) => {
return self.handle_submenu(ctx, index); match self.menu_screen.event(ctx, event) {
} Some(VerticalMenuScreenMsg::Selected(index)) => {
Some(VerticalMenuScreenMsg::Back) => { if let Subscreen::DeviceScreen(_, i) = subscreen {
return self.go_back(); if index == DISCONNECT_DEVICE_MENU_INDEX {
} return Some(DeviceMenuMsg::DeviceDisconnect(*i));
Some(VerticalMenuScreenMsg::Close) => { }
return Some(DeviceMenuMsg::Close); } else {
} return self.handle_submenu(ctx, index);
_ => {} }
},
Subscreen::DeviceScreen(_, i) => match self.paired_device_screen.event(ctx, event) {
Some(VerticalMenuScreenMsg::Selected(index)) => {
if index == DISCONNECT_DEVICE_MENU_INDEX {
return Some(DeviceMenuMsg::DeviceDisconnect(i));
} }
Some(VerticalMenuScreenMsg::Back) => {
return self.go_back();
}
Some(VerticalMenuScreenMsg::Close) => {
return Some(DeviceMenuMsg::Close);
}
_ => {}
} }
Some(VerticalMenuScreenMsg::Back) => { }
return self.go_back();
}
Some(VerticalMenuScreenMsg::Close) => {
return Some(DeviceMenuMsg::Close);
}
_ => {}
},
Subscreen::AboutScreen => { Subscreen::AboutScreen => {
if let Some(TextScreenMsg::Cancelled) = self.about_screen.event(ctx, event) { if let Some(TextScreenMsg::Cancelled) = self.about_screen.event(ctx, event) {
return self.go_back(); return self.go_back();
@ -515,8 +503,7 @@ impl<'a> Component for DeviceMenuScreen<'a> {
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
match &self.subscreens[self.active_subscreen] { match &self.subscreens[self.active_subscreen] {
Subscreen::Submenu(..) => self.menu_screen.render(target), Subscreen::Submenu(..) | Subscreen::DeviceScreen(..) => self.menu_screen.render(target),
Subscreen::DeviceScreen(..) => self.paired_device_screen.render(target),
Subscreen::AboutScreen => self.about_screen.render(target), Subscreen::AboutScreen => self.about_screen.render(target),
} }
} }

View File

@ -796,7 +796,7 @@ impl FirmwareUI for UIEckhart {
firmware_version, firmware_version,
device_name, device_name,
paired_devices, paired_devices,
)); )?);
Ok(layout) Ok(layout)
} }