1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-30 18:38:27 +00:00

feat(eckhart): lazy building of device menu screens

This commit is contained in:
Ioan Bizău 2025-04-01 12:16:54 +02:00
parent f519d1f073
commit 18e7603491
8 changed files with 291 additions and 208 deletions

View File

@ -807,7 +807,7 @@ extern "C" fn new_show_device_menu(n_args: usize, args: *const Obj, kwargs: *mut
let failed_backup: bool = kwargs.get(Qstr::MP_QSTR_failed_backup)?.try_into()?;
let battery_percentage: usize = kwargs.get_or(Qstr::MP_QSTR_battery_percentage, 0)?;
let paired_devices: Obj = kwargs.get(Qstr::MP_QSTR_paired_devices)?;
let paired_devices: Vec<TString, 10> = util::iter_into_vec(paired_devices)?;
let paired_devices: Vec<TString, 1> = util::iter_into_vec(paired_devices)?;
let layout = ModelUI::show_device_menu(failed_backup, battery_percentage, paired_devices)?;
let layout_obj = LayoutObj::new_root(layout)?;
Ok(layout_obj.into())

View File

@ -870,7 +870,7 @@ impl FirmwareUI for UIBolt {
Ok(layout)
}
fn show_device_menu(_failed_backup: bool, _battery_percentage: usize, _paired_devices: Vec<TString<'static>, 10>) -> Result<impl LayoutMaybeTrace, Error> {
fn show_device_menu(_failed_backup: bool, _battery_percentage: usize, _paired_devices: Vec<TString<'static>, 1>) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_device_menu not supported"))
}

View File

@ -1036,7 +1036,7 @@ impl FirmwareUI for UICaesar {
Ok(layout)
}
fn show_device_menu(_failed_backup: bool, _battery_percentage: usize, _paired_devices: Vec<TString<'static>, 10>) -> Result<impl LayoutMaybeTrace, Error> {
fn show_device_menu(_failed_backup: bool, _battery_percentage: usize, _paired_devices: Vec<TString<'static>, 1>) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_device_menu not supported"))
}

View File

@ -888,7 +888,7 @@ impl FirmwareUI for UIDelizia {
Ok(layout)
}
fn show_device_menu(_failed_backup: bool, _battery_percentage: usize, _paired_devices: Vec<TString<'static>, 10>) -> Result<impl LayoutMaybeTrace, Error> {
fn show_device_menu(_failed_backup: bool, _battery_percentage: usize, _paired_devices: Vec<TString<'static>, 1>) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_device_menu not supported"))
}

View File

@ -139,9 +139,9 @@ impl ComponentMsgObj for SetBrightnessScreen {
impl<'a> ComponentMsgObj for DeviceMenuScreen<'a> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
DeviceMenuMsg::NotImplemented => "NotImplemented".try_into(),
DeviceMenuMsg::BackupFailed => "BackupFailed".try_into(),
DeviceMenuMsg::PairNewDevice => "PairNewDevice".try_into(),
DeviceMenuMsg::DevicePair => "DevicePair".try_into(),
DeviceMenuMsg::DeviceDisconnect(_) => "DeviceDisconnect".try_into(),
DeviceMenuMsg::CheckBackup => "CheckBackup".try_into(),
DeviceMenuMsg::WipeDevice => "WipeDevice".try_into(),
DeviceMenuMsg::ScreenBrightness => "ScreenBrightness".try_into(),

View File

@ -8,7 +8,7 @@ use crate::{
},
geometry::Rect,
layout_eckhart::{
component::Button,
component::{Button, ButtonStyleSheet},
constant::SCREEN,
firmware::{
Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
@ -23,38 +23,29 @@ use super::theme;
use heapless::Vec;
const MAX_DEPTH: usize = 5;
const MAX_SUBMENUS: usize = 10;
const MAX_SUBSCREENS: usize = 10;
#[derive(Clone, Debug)]
const DISCONNECT_DEVICE_MENU_INDEX: usize = 1;
#[derive(Clone)]
enum Action {
// Go to another registered child screen
// Go to another registered subscreen
GoTo(usize),
// Return a DeviceMenuMsg to the caller
Return(DeviceMenuMsg),
}
struct SubmenuScreen {
pub screen: VerticalMenuScreen,
// actions for the menu items in the VerticalMenuScreen, in order
pub actions: Vec<Option<Action>, MENU_MAX_ITEMS>,
}
enum Subscreen {
Submenu(SubmenuScreen),
AboutScreen,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone)]
pub enum DeviceMenuMsg {
NotImplemented,
// Root menu
BackupFailed,
// "Pair & Connect"
PairNewDevice,
DevicePair, // pair a new device
DeviceDisconnect(
usize, /* which device to disconnect, index in the list of devices */
),
// Security menu
CheckBackup,
@ -63,20 +54,99 @@ pub enum DeviceMenuMsg {
// Device menu
ScreenBrightness,
// nothing selected
Close,
}
struct MenuItem {
text: TString<'static>,
subtext: Option<TString<'static>>,
stylesheet: ButtonStyleSheet,
green_subtext: bool,
action: Option<Action>,
}
impl MenuItem {
pub fn new(text: TString<'static>, action: Option<Action>) -> Self {
Self {
text,
subtext: None,
stylesheet: theme::menu_item_title(),
green_subtext: false,
action,
}
}
pub fn with_subtext(mut self, subtext: Option<TString<'static>>) -> Self {
self.subtext = subtext;
self
}
pub fn with_stylesheet(mut self, stylesheet: ButtonStyleSheet) -> Self {
self.stylesheet = stylesheet;
self
}
pub fn with_green_subtext(mut self) -> Self {
self.green_subtext = true;
self
}
}
struct SubmenuScreen {
header_text: TString<'static>,
show_battery: bool,
items: Vec<MenuItem, MENU_MAX_ITEMS>,
}
impl SubmenuScreen {
pub fn new(header_text: TString<'static>, items: Vec<MenuItem, MENU_MAX_ITEMS>) -> Self {
Self {
header_text,
show_battery: false,
items,
}
}
pub fn with_battery(mut self) -> Self {
self.show_battery = true;
self
}
}
// Each subscreen of the DeviceMenuScreen is one of these
enum Subscreen {
// A menu, with associated items and actions
Submenu(SubmenuScreen),
// A screen allowing the user to to disconnect a device
DeviceScreen(
TString<'static>, /* device name */
usize, /* index in the list of devices */
),
// The about screen
AboutScreen,
}
pub struct DeviceMenuScreen<'a> {
bounds: Rect,
about_screen: TextScreen<Paragraphs<[Paragraph<'a>; 2]>>,
battery_percentage: usize,
// all the subscreens in the DeviceMenuScreen
// which can be either VerticalMenuScreens with associated Actions
// or some predefined TextScreens, such as "About"
subscreens: Vec<Subscreen, MAX_SUBMENUS>,
// These correspond to the currently active subscreen,
// which is one of the possible kinds of subscreens
// as defined by `enum Subscreen`
// The active one will be Some(...) and the other two will be None.
// This way we only need to keep one screen at any time in memory.
menu_screen: Option<VerticalMenuScreen>,
paired_device_screen: Option<VerticalMenuScreen>,
about_screen: Option<TextScreen<Paragraphs<[Paragraph<'a>; 2]>>>,
// the index of the current subscreen in the list of subscreens
// Information needed to construct any subscreen on demand
subscreens: Vec<Subscreen, MAX_SUBSCREENS>,
// index of the current subscreen in the list of subscreens
active_subscreen: usize,
// stack of parents that led to the current subscreen
@ -87,19 +157,18 @@ impl<'a> DeviceMenuScreen<'a> {
pub fn new(
failed_backup: bool,
battery_percentage: usize,
paired_devices: Vec<TString<'static>, 10>,
// NB: we currently only support one device at a time.
// if we ever increase this size, we will need a way to return the correct
// device index on Disconnect back to uPy
// (see component_msg_obj.rs, which currently just returns "DeviceDisconnect" with no index!)
paired_devices: Vec<TString<'static>, 1>,
) -> Self {
let about_content = Paragraphs::new([
Paragraph::new(&theme::firmware::TEXT_REGULAR, "Firmware version"),
Paragraph::new(&theme::firmware::TEXT_REGULAR, "2.3.1"), // TODO
]);
let about_screen = TextScreen::new(about_content)
.with_header(Header::new("About".into()).with_close_button());
let mut screen = Self {
bounds: Rect::zero(),
about_screen,
battery_percentage,
menu_screen: None,
paired_device_screen: None,
about_screen: None,
active_subscreen: 0,
subscreens: Vec::new(),
parent_subscreens: Vec::new(),
@ -110,220 +179,147 @@ impl<'a> DeviceMenuScreen<'a> {
let device = screen.add_device_menu("My device".into(), about); // TODO: device name
let settings = screen.add_settings_menu(security, device);
let mut paired_device_indices: Vec<usize, 10> = Vec::new();
for device in &paired_devices {
unwrap!(paired_device_indices.push(screen.add_paired_device_menu(*device)));
let mut paired_device_indices: Vec<usize, 1> = Vec::new();
for (i, device) in paired_devices.iter().enumerate() {
unwrap!(paired_device_indices
.push(screen.add_subscreen(Subscreen::DeviceScreen(*device, i))));
}
let devices = screen.add_paired_devices_menu(paired_devices, paired_device_indices);
let pair_and_connect = screen.add_pair_and_connect_menu(devices);
let root = screen.add_root_menu(
failed_backup,
battery_percentage,
pair_and_connect,
settings,
);
let root = screen.add_root_menu(failed_backup, pair_and_connect, settings);
screen.set_active_subscreen(root);
screen
}
fn add_paired_device_menu(&mut self, device: TString<'static>) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(Button::new_menu_item(
device.into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(None));
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("Manage".into())
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
}
fn add_paired_devices_menu(
&mut self,
paired_devices: Vec<TString<'static>, 10>,
paired_device_indices: Vec<usize, 10>,
paired_devices: Vec<TString<'static>, 1>,
paired_device_indices: Vec<usize, 1>,
) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
for (device, idx) in paired_devices.iter().zip(paired_device_indices) {
menu = menu.item(Button::new_menu_item(
(*device).into(),
None,
theme::menu_item_title(),
unwrap!(items.push(
MenuItem::new((*device).into(), Some(Action::GoTo(idx)))
.with_subtext(Some("Connected".into())) // TODO: this should be a boolean feature of the device
.with_green_subtext()
));
unwrap!(actions.push(Some(Action::GoTo(idx))));
}
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("Manage paired devices".into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
"Manage paired devices".into(),
items,
)))
}
fn add_pair_and_connect_menu(&mut self, manage_devices_index: usize) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(
Button::new_menu_item(
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
unwrap!(items.push(
MenuItem::new(
"Manage paired devices".into(),
Some("1 device connected".into()), // TODO
theme::menu_item_title(),
Some(Action::GoTo(manage_devices_index)),
)
.subtext_green(),
);
unwrap!(actions.push(Some(Action::GoTo(manage_devices_index))));
menu = menu.item(Button::new_menu_item(
"Pair new device".into(),
None,
theme::menu_item_title(),
.with_subtext(Some("1 device connected".into()))
.with_green_subtext(),
));
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::PairNewDevice))));
unwrap!(items.push(MenuItem::new(
"Pair new device".into(),
Some(Action::Return(DeviceMenuMsg::DevicePair)),
)));
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("Pair & Connect".into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
"Pair & connect".into(),
items,
)))
}
fn add_settings_menu(&mut self, security_index: usize, device_index: usize) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(Button::new_menu_item(
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
unwrap!(items.push(MenuItem::new(
"Security".into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(Some(Action::GoTo(security_index))));
menu = menu.item(Button::new_menu_item(
Some(Action::GoTo(security_index))
)));
unwrap!(items.push(MenuItem::new(
"Device".into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(Some(Action::GoTo(device_index))));
Some(Action::GoTo(device_index))
)));
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("Settings".into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
"Settings".into(),
items,
)))
}
fn add_security_menu(&mut self) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(Button::new_menu_item(
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
unwrap!(items.push(MenuItem::new(
"Check backup".into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::CheckBackup))));
menu = menu.item(Button::new_menu_item(
Some(Action::Return(DeviceMenuMsg::CheckBackup)),
)));
unwrap!(items.push(MenuItem::new(
"Wipe device".into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::WipeDevice))));
Some(Action::Return(DeviceMenuMsg::WipeDevice))
)));
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("Security".into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
"Security".into(),
items,
)))
}
fn add_device_menu(&mut self, device_name: TString<'static>, about_index: usize) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(Button::new_menu_item(
"Name".into(),
Some(device_name),
theme::menu_item_title(),
));
unwrap!(actions.push(None));
menu = menu.item(Button::new_menu_item(
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
unwrap!(items.push(MenuItem::new("Name".into(), None).with_subtext(Some(device_name))));
unwrap!(items.push(MenuItem::new(
"Screen brightness".into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::ScreenBrightness))));
menu = menu.item(Button::new_menu_item(
Some(Action::Return(DeviceMenuMsg::ScreenBrightness)),
)));
unwrap!(items.push(MenuItem::new(
"About".into(),
None,
theme::menu_item_title(),
));
unwrap!(actions.push(Some(Action::GoTo(about_index))));
Some(Action::GoTo(about_index))
)));
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("Security".into())
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
"Device".into(),
items,
)))
}
fn add_root_menu(
&mut self,
failed_backup: bool,
battery_percentage: usize,
pair_and_connect_index: usize,
settings_index: usize,
) -> usize {
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
let mut menu = VerticalMenu::empty().with_separators();
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
if failed_backup {
menu = menu.item(Button::new_menu_item(
"Backup failed".into(),
Some("Review".into()),
theme::menu_item_title_red(),
unwrap!(items.push(
MenuItem::new(
"Backup failed".into(),
Some(Action::Return(DeviceMenuMsg::BackupFailed)),
)
.with_subtext(Some("Review".into()))
.with_stylesheet(theme::menu_item_title_red()),
));
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::BackupFailed))));
}
menu = menu.item(
Button::new_menu_item(
unwrap!(items.push(
MenuItem::new(
"Pair & connect".into(),
Some("1 device connected".into()), // TODO
theme::menu_item_title(),
Some(Action::GoTo(pair_and_connect_index)),
)
.subtext_green(),
);
unwrap!(actions.push(Some(Action::GoTo(pair_and_connect_index))));
menu = menu.item(Button::new_menu_item(
"Settings".into(),
None,
theme::menu_item_title(),
.with_subtext(Some("1 device connected".into()))
.with_green_subtext(),
));
unwrap!(actions.push(Some(Action::GoTo(settings_index))));
let screen = VerticalMenuScreen::new(menu).with_header(
Header::new("".into())
.with_battery(battery_percentage)
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
);
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
unwrap!(items.push(MenuItem::new(
"Settings".into(),
Some(Action::GoTo(settings_index)),
)));
self.add_subscreen(Subscreen::Submenu(
SubmenuScreen::new("".into(), items).with_battery(),
))
}
fn add_subscreen(&mut self, screen: Subscreen) -> usize {
@ -334,16 +330,87 @@ impl<'a> DeviceMenuScreen<'a> {
fn set_active_subscreen(&mut self, idx: usize) {
assert!(idx < self.subscreens.len());
self.active_subscreen = idx;
self.build_active_subscreen();
}
fn build_active_subscreen(&mut self) {
match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut submenu) => {
self.paired_device_screen = None;
self.about_screen = None;
let mut menu = VerticalMenu::empty().with_separators();
for item in &submenu.items {
let mut button =
Button::new_menu_item(item.text, item.subtext, item.stylesheet);
if item.green_subtext {
button = button.subtext_green();
}
menu = menu.item(button);
}
let mut header = Header::new(submenu.header_text)
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled);
if submenu.show_battery {
header = header.with_battery(self.battery_percentage);
} else {
header = header.with_left_button(
Button::with_icon(theme::ICON_CHEVRON_LEFT),
HeaderMsg::Back,
);
}
self.menu_screen = Some(VerticalMenuScreen::new(menu).with_header(header));
}
Subscreen::DeviceScreen(device, _) => {
self.menu_screen = None;
self.about_screen = None;
let mut menu = VerticalMenu::empty().with_separators();
menu = menu.item(Button::new_menu_item(
device,
None,
theme::menu_item_title(),
));
menu = menu.item(Button::new_menu_item(
"Disconnect".into(),
None,
theme::menu_item_title_red(),
));
self.paired_device_screen = Some(
VerticalMenuScreen::new(menu).with_header(
Header::new("Manage".into())
.with_right_button(
Button::with_icon(theme::ICON_CROSS),
HeaderMsg::Cancelled,
)
.with_left_button(
Button::with_icon(theme::ICON_CHEVRON_LEFT),
HeaderMsg::Back,
),
),
);
}
Subscreen::AboutScreen => {
self.menu_screen = None;
self.paired_device_screen = None;
let about_content = Paragraphs::new([
Paragraph::new(&theme::firmware::TEXT_REGULAR, "Firmware version"),
Paragraph::new(&theme::firmware::TEXT_REGULAR, "2.3.1"), // TODO
]);
self.about_screen = Some(
TextScreen::new(about_content)
.with_header(Header::new("About".into()).with_close_button()),
);
}
}
}
fn handle_submenu(&mut self, ctx: &mut EventCtx, idx: usize) -> Option<DeviceMenuMsg> {
match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut menu_screen) => {
match menu_screen.actions[idx] {
match menu_screen.items[idx].action {
Some(Action::GoTo(menu)) => {
menu_screen.screen.update_menu(ctx);
self.menu_screen.as_mut().unwrap().update_menu(ctx);
unwrap!(self.parent_subscreens.push(self.active_subscreen));
self.active_subscreen = menu;
self.set_active_subscreen(menu);
self.place(self.bounds);
}
Some(Action::Return(msg)) => return Some(msg),
@ -360,7 +427,7 @@ impl<'a> DeviceMenuScreen<'a> {
fn go_back(&mut self) -> Option<DeviceMenuMsg> {
if let Some(parent) = self.parent_subscreens.pop() {
self.active_subscreen = parent;
self.set_active_subscreen(parent);
self.place(self.bounds);
None
} else {
@ -380,7 +447,8 @@ impl<'a> Component for DeviceMenuScreen<'a> {
self.bounds = bounds;
match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut menu_screen) => menu_screen.screen.place(bounds),
Subscreen::Submenu(..) => self.menu_screen.place(bounds),
Subscreen::DeviceScreen(..) => self.paired_device_screen.place(bounds),
Subscreen::AboutScreen => self.about_screen.place(bounds),
};
@ -393,7 +461,7 @@ impl<'a> Component for DeviceMenuScreen<'a> {
// Handle the event for the active menu
match self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref mut menu_screen) => match menu_screen.screen.event(ctx, event) {
Subscreen::Submenu(..) => match self.menu_screen.event(ctx, event) {
Some(VerticalMenuScreenMsg::Selected(index)) => {
return self.handle_submenu(ctx, index);
}
@ -405,6 +473,20 @@ impl<'a> Component for DeviceMenuScreen<'a> {
}
_ => {}
},
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);
}
_ => {}
},
Subscreen::AboutScreen => match self.about_screen.event(ctx, event) {
Some(TextScreenMsg::Cancelled) => {
return self.go_back();
@ -418,7 +500,8 @@ impl<'a> Component for DeviceMenuScreen<'a> {
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
match &self.subscreens[self.active_subscreen] {
Subscreen::Submenu(ref menu_screen) => menu_screen.screen.render(target),
Subscreen::Submenu(..) => self.menu_screen.render(target),
Subscreen::DeviceScreen(..) => self.paired_device_screen.render(target),
Subscreen::AboutScreen => self.about_screen.render(target),
}
}

View File

@ -783,7 +783,7 @@ impl FirmwareUI for UIEckhart {
Ok(layout)
}
fn show_device_menu(failed_backup: bool, battery_percentage: usize, paired_devices: Vec<TString<'static>, 10>) -> Result<impl LayoutMaybeTrace, Error> {
fn show_device_menu(failed_backup: bool, battery_percentage: usize, paired_devices: Vec<TString<'static>, 1>) -> Result<impl LayoutMaybeTrace, Error> {
let layout = RootComponent::new(DeviceMenuScreen::new(failed_backup, battery_percentage, paired_devices));
Ok(layout)
}

View File

@ -304,7 +304,7 @@ pub trait FirmwareUI {
fn show_device_menu(
failed_backup: bool,
battery_percentage: usize,
paired_devices: Vec<TString<'static>, 10>,
paired_devices: Vec<TString<'static>, 1>,
) -> Result<impl LayoutMaybeTrace, Error>;
fn show_info(