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:
parent
1029fe684c
commit
07424f1625
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -796,7 +796,7 @@ impl FirmwareUI for UIEckhart {
|
|||||||
firmware_version,
|
firmware_version,
|
||||||
device_name,
|
device_name,
|
||||||
paired_devices,
|
paired_devices,
|
||||||
));
|
)?);
|
||||||
Ok(layout)
|
Ok(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user