mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-02 11:58:32 +00:00
feat(eckhart): lazy building of device menu screens
This commit is contained in:
parent
f519d1f073
commit
18e7603491
@ -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 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 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: 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 = ModelUI::show_device_menu(failed_backup, battery_percentage, paired_devices)?;
|
||||||
let layout_obj = LayoutObj::new_root(layout)?;
|
let layout_obj = LayoutObj::new_root(layout)?;
|
||||||
Ok(layout_obj.into())
|
Ok(layout_obj.into())
|
||||||
|
@ -870,7 +870,7 @@ impl FirmwareUI for UIBolt {
|
|||||||
Ok(layout)
|
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"))
|
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_device_menu not supported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1036,7 +1036,7 @@ impl FirmwareUI for UICaesar {
|
|||||||
Ok(layout)
|
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"))
|
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_device_menu not supported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,7 +888,7 @@ impl FirmwareUI for UIDelizia {
|
|||||||
Ok(layout)
|
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"))
|
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"show_device_menu not supported"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,9 +139,9 @@ impl ComponentMsgObj for SetBrightnessScreen {
|
|||||||
impl<'a> ComponentMsgObj for DeviceMenuScreen<'a> {
|
impl<'a> ComponentMsgObj for DeviceMenuScreen<'a> {
|
||||||
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
match msg {
|
match msg {
|
||||||
DeviceMenuMsg::NotImplemented => "NotImplemented".try_into(),
|
|
||||||
DeviceMenuMsg::BackupFailed => "BackupFailed".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::CheckBackup => "CheckBackup".try_into(),
|
||||||
DeviceMenuMsg::WipeDevice => "WipeDevice".try_into(),
|
DeviceMenuMsg::WipeDevice => "WipeDevice".try_into(),
|
||||||
DeviceMenuMsg::ScreenBrightness => "ScreenBrightness".try_into(),
|
DeviceMenuMsg::ScreenBrightness => "ScreenBrightness".try_into(),
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
geometry::Rect,
|
geometry::Rect,
|
||||||
layout_eckhart::{
|
layout_eckhart::{
|
||||||
component::Button,
|
component::{Button, ButtonStyleSheet},
|
||||||
constant::SCREEN,
|
constant::SCREEN,
|
||||||
firmware::{
|
firmware::{
|
||||||
Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
|
Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen,
|
||||||
@ -23,38 +23,29 @@ use super::theme;
|
|||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
|
|
||||||
const MAX_DEPTH: usize = 5;
|
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 {
|
enum Action {
|
||||||
// Go to another registered child screen
|
// Go to another registered subscreen
|
||||||
GoTo(usize),
|
GoTo(usize),
|
||||||
|
|
||||||
// Return a DeviceMenuMsg to the caller
|
// Return a DeviceMenuMsg to the caller
|
||||||
Return(DeviceMenuMsg),
|
Return(DeviceMenuMsg),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SubmenuScreen {
|
#[derive(Copy, Clone)]
|
||||||
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)]
|
|
||||||
pub enum DeviceMenuMsg {
|
pub enum DeviceMenuMsg {
|
||||||
NotImplemented,
|
|
||||||
|
|
||||||
// Root menu
|
// Root menu
|
||||||
BackupFailed,
|
BackupFailed,
|
||||||
|
|
||||||
// "Pair & Connect"
|
// "Pair & Connect"
|
||||||
PairNewDevice,
|
DevicePair, // pair a new device
|
||||||
|
DeviceDisconnect(
|
||||||
|
usize, /* which device to disconnect, index in the list of devices */
|
||||||
|
),
|
||||||
|
|
||||||
// Security menu
|
// Security menu
|
||||||
CheckBackup,
|
CheckBackup,
|
||||||
@ -63,20 +54,99 @@ pub enum DeviceMenuMsg {
|
|||||||
// Device menu
|
// Device menu
|
||||||
ScreenBrightness,
|
ScreenBrightness,
|
||||||
|
|
||||||
|
// nothing selected
|
||||||
Close,
|
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> {
|
pub struct DeviceMenuScreen<'a> {
|
||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
|
|
||||||
about_screen: TextScreen<Paragraphs<[Paragraph<'a>; 2]>>,
|
battery_percentage: usize,
|
||||||
|
|
||||||
// all the subscreens in the DeviceMenuScreen
|
// These correspond to the currently active subscreen,
|
||||||
// which can be either VerticalMenuScreens with associated Actions
|
// which is one of the possible kinds of subscreens
|
||||||
// or some predefined TextScreens, such as "About"
|
// as defined by `enum Subscreen`
|
||||||
subscreens: Vec<Subscreen, MAX_SUBMENUS>,
|
// 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,
|
active_subscreen: usize,
|
||||||
|
|
||||||
// stack of parents that led to the current subscreen
|
// stack of parents that led to the current subscreen
|
||||||
@ -87,19 +157,18 @@ impl<'a> DeviceMenuScreen<'a> {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
failed_backup: bool,
|
failed_backup: bool,
|
||||||
battery_percentage: usize,
|
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 {
|
) -> 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 {
|
let mut screen = Self {
|
||||||
bounds: Rect::zero(),
|
bounds: Rect::zero(),
|
||||||
about_screen,
|
battery_percentage,
|
||||||
|
menu_screen: None,
|
||||||
|
paired_device_screen: None,
|
||||||
|
about_screen: None,
|
||||||
active_subscreen: 0,
|
active_subscreen: 0,
|
||||||
subscreens: Vec::new(),
|
subscreens: Vec::new(),
|
||||||
parent_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 device = screen.add_device_menu("My device".into(), about); // TODO: device name
|
||||||
let settings = screen.add_settings_menu(security, device);
|
let settings = screen.add_settings_menu(security, device);
|
||||||
|
|
||||||
let mut paired_device_indices: Vec<usize, 10> = Vec::new();
|
let mut paired_device_indices: Vec<usize, 1> = Vec::new();
|
||||||
for device in &paired_devices {
|
for (i, device) in paired_devices.iter().enumerate() {
|
||||||
unwrap!(paired_device_indices.push(screen.add_paired_device_menu(*device)));
|
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 devices = screen.add_paired_devices_menu(paired_devices, paired_device_indices);
|
||||||
let pair_and_connect = screen.add_pair_and_connect_menu(devices);
|
let pair_and_connect = screen.add_pair_and_connect_menu(devices);
|
||||||
|
|
||||||
let root = screen.add_root_menu(
|
let root = screen.add_root_menu(failed_backup, pair_and_connect, settings);
|
||||||
failed_backup,
|
|
||||||
battery_percentage,
|
|
||||||
pair_and_connect,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
|
|
||||||
screen.set_active_subscreen(root);
|
screen.set_active_subscreen(root);
|
||||||
|
|
||||||
screen
|
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(
|
fn add_paired_devices_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
paired_devices: Vec<TString<'static>, 10>,
|
paired_devices: Vec<TString<'static>, 1>,
|
||||||
paired_device_indices: Vec<usize, 10>,
|
paired_device_indices: Vec<usize, 1>,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
|
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
|
||||||
let mut menu = VerticalMenu::empty().with_separators();
|
|
||||||
for (device, idx) in paired_devices.iter().zip(paired_device_indices) {
|
for (device, idx) in paired_devices.iter().zip(paired_device_indices) {
|
||||||
menu = menu.item(Button::new_menu_item(
|
unwrap!(items.push(
|
||||||
(*device).into(),
|
MenuItem::new((*device).into(), Some(Action::GoTo(idx)))
|
||||||
None,
|
.with_subtext(Some("Connected".into())) // TODO: this should be a boolean feature of the device
|
||||||
theme::menu_item_title(),
|
.with_green_subtext()
|
||||||
));
|
));
|
||||||
unwrap!(actions.push(Some(Action::GoTo(idx))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let screen = VerticalMenuScreen::new(menu).with_header(
|
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
|
||||||
Header::new("Manage paired devices".into())
|
"Manage paired devices".into(),
|
||||||
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
|
items,
|
||||||
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
|
)))
|
||||||
);
|
|
||||||
|
|
||||||
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
|
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
|
||||||
let mut menu = VerticalMenu::empty().with_separators();
|
unwrap!(items.push(
|
||||||
menu = menu.item(
|
MenuItem::new(
|
||||||
Button::new_menu_item(
|
|
||||||
"Manage paired devices".into(),
|
"Manage paired devices".into(),
|
||||||
Some("1 device connected".into()), // TODO
|
Some(Action::GoTo(manage_devices_index)),
|
||||||
theme::menu_item_title(),
|
|
||||||
)
|
)
|
||||||
.subtext_green(),
|
.with_subtext(Some("1 device connected".into()))
|
||||||
);
|
.with_green_subtext(),
|
||||||
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(),
|
|
||||||
));
|
));
|
||||||
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(
|
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
|
||||||
Header::new("Pair & Connect".into())
|
"Pair & connect".into(),
|
||||||
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
|
items,
|
||||||
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
|
)))
|
||||||
);
|
|
||||||
|
|
||||||
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
|
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
|
||||||
let mut menu = VerticalMenu::empty().with_separators();
|
unwrap!(items.push(MenuItem::new(
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"Security".into(),
|
"Security".into(),
|
||||||
None,
|
Some(Action::GoTo(security_index))
|
||||||
theme::menu_item_title(),
|
)));
|
||||||
));
|
unwrap!(items.push(MenuItem::new(
|
||||||
unwrap!(actions.push(Some(Action::GoTo(security_index))));
|
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"Device".into(),
|
"Device".into(),
|
||||||
None,
|
Some(Action::GoTo(device_index))
|
||||||
theme::menu_item_title(),
|
)));
|
||||||
));
|
|
||||||
unwrap!(actions.push(Some(Action::GoTo(device_index))));
|
|
||||||
|
|
||||||
let screen = VerticalMenuScreen::new(menu).with_header(
|
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
|
||||||
Header::new("Settings".into())
|
"Settings".into(),
|
||||||
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
|
items,
|
||||||
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
|
)))
|
||||||
);
|
|
||||||
|
|
||||||
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_security_menu(&mut self) -> usize {
|
fn add_security_menu(&mut self) -> usize {
|
||||||
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
|
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
|
||||||
let mut menu = VerticalMenu::empty().with_separators();
|
unwrap!(items.push(MenuItem::new(
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"Check backup".into(),
|
"Check backup".into(),
|
||||||
None,
|
Some(Action::Return(DeviceMenuMsg::CheckBackup)),
|
||||||
theme::menu_item_title(),
|
)));
|
||||||
));
|
unwrap!(items.push(MenuItem::new(
|
||||||
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::CheckBackup))));
|
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"Wipe device".into(),
|
"Wipe device".into(),
|
||||||
None,
|
Some(Action::Return(DeviceMenuMsg::WipeDevice))
|
||||||
theme::menu_item_title(),
|
)));
|
||||||
));
|
|
||||||
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::WipeDevice))));
|
|
||||||
|
|
||||||
let screen = VerticalMenuScreen::new(menu).with_header(
|
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
|
||||||
Header::new("Security".into())
|
"Security".into(),
|
||||||
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
|
items,
|
||||||
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
|
)))
|
||||||
);
|
|
||||||
|
|
||||||
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
|
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
|
||||||
let mut menu = VerticalMenu::empty().with_separators();
|
unwrap!(items.push(MenuItem::new("Name".into(), None).with_subtext(Some(device_name))));
|
||||||
menu = menu.item(Button::new_menu_item(
|
unwrap!(items.push(MenuItem::new(
|
||||||
"Name".into(),
|
|
||||||
Some(device_name),
|
|
||||||
theme::menu_item_title(),
|
|
||||||
));
|
|
||||||
unwrap!(actions.push(None));
|
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"Screen brightness".into(),
|
"Screen brightness".into(),
|
||||||
None,
|
Some(Action::Return(DeviceMenuMsg::ScreenBrightness)),
|
||||||
theme::menu_item_title(),
|
)));
|
||||||
));
|
unwrap!(items.push(MenuItem::new(
|
||||||
unwrap!(actions.push(Some(Action::Return(DeviceMenuMsg::ScreenBrightness))));
|
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"About".into(),
|
"About".into(),
|
||||||
None,
|
Some(Action::GoTo(about_index))
|
||||||
theme::menu_item_title(),
|
)));
|
||||||
));
|
|
||||||
unwrap!(actions.push(Some(Action::GoTo(about_index))));
|
|
||||||
|
|
||||||
let screen = VerticalMenuScreen::new(menu).with_header(
|
self.add_subscreen(Subscreen::Submenu(SubmenuScreen::new(
|
||||||
Header::new("Security".into())
|
"Device".into(),
|
||||||
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled)
|
items,
|
||||||
.with_left_button(Button::with_icon(theme::ICON_CHEVRON_LEFT), HeaderMsg::Back),
|
)))
|
||||||
);
|
|
||||||
|
|
||||||
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_root_menu(
|
fn add_root_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
failed_backup: bool,
|
failed_backup: bool,
|
||||||
battery_percentage: usize,
|
|
||||||
pair_and_connect_index: usize,
|
pair_and_connect_index: usize,
|
||||||
settings_index: usize,
|
settings_index: usize,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let mut actions: Vec<Option<Action>, MENU_MAX_ITEMS> = Vec::new();
|
let mut items: Vec<MenuItem, MENU_MAX_ITEMS> = Vec::new();
|
||||||
let mut menu = VerticalMenu::empty().with_separators();
|
|
||||||
if failed_backup {
|
if failed_backup {
|
||||||
menu = menu.item(Button::new_menu_item(
|
unwrap!(items.push(
|
||||||
"Backup failed".into(),
|
MenuItem::new(
|
||||||
Some("Review".into()),
|
"Backup failed".into(),
|
||||||
theme::menu_item_title_red(),
|
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))));
|
|
||||||
}
|
}
|
||||||
|
unwrap!(items.push(
|
||||||
menu = menu.item(
|
MenuItem::new(
|
||||||
Button::new_menu_item(
|
|
||||||
"Pair & connect".into(),
|
"Pair & connect".into(),
|
||||||
Some("1 device connected".into()), // TODO
|
Some(Action::GoTo(pair_and_connect_index)),
|
||||||
theme::menu_item_title(),
|
|
||||||
)
|
)
|
||||||
.subtext_green(),
|
.with_subtext(Some("1 device connected".into()))
|
||||||
);
|
.with_green_subtext(),
|
||||||
unwrap!(actions.push(Some(Action::GoTo(pair_and_connect_index))));
|
|
||||||
|
|
||||||
menu = menu.item(Button::new_menu_item(
|
|
||||||
"Settings".into(),
|
|
||||||
None,
|
|
||||||
theme::menu_item_title(),
|
|
||||||
));
|
));
|
||||||
unwrap!(actions.push(Some(Action::GoTo(settings_index))));
|
unwrap!(items.push(MenuItem::new(
|
||||||
|
"Settings".into(),
|
||||||
let screen = VerticalMenuScreen::new(menu).with_header(
|
Some(Action::GoTo(settings_index)),
|
||||||
Header::new("".into())
|
)));
|
||||||
.with_battery(battery_percentage)
|
self.add_subscreen(Subscreen::Submenu(
|
||||||
.with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled),
|
SubmenuScreen::new("".into(), items).with_battery(),
|
||||||
);
|
))
|
||||||
|
|
||||||
self.add_subscreen(Subscreen::Submenu(SubmenuScreen { screen, actions }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_subscreen(&mut self, screen: Subscreen) -> usize {
|
fn add_subscreen(&mut self, screen: Subscreen) -> usize {
|
||||||
@ -334,16 +330,87 @@ impl<'a> DeviceMenuScreen<'a> {
|
|||||||
fn set_active_subscreen(&mut self, idx: usize) {
|
fn set_active_subscreen(&mut self, idx: usize) {
|
||||||
assert!(idx < self.subscreens.len());
|
assert!(idx < self.subscreens.len());
|
||||||
self.active_subscreen = idx;
|
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> {
|
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 menu_screen) => {
|
||||||
match menu_screen.actions[idx] {
|
match menu_screen.items[idx].action {
|
||||||
Some(Action::GoTo(menu)) => {
|
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));
|
unwrap!(self.parent_subscreens.push(self.active_subscreen));
|
||||||
self.active_subscreen = menu;
|
self.set_active_subscreen(menu);
|
||||||
self.place(self.bounds);
|
self.place(self.bounds);
|
||||||
}
|
}
|
||||||
Some(Action::Return(msg)) => return Some(msg),
|
Some(Action::Return(msg)) => return Some(msg),
|
||||||
@ -360,7 +427,7 @@ impl<'a> DeviceMenuScreen<'a> {
|
|||||||
|
|
||||||
fn go_back(&mut self) -> Option<DeviceMenuMsg> {
|
fn go_back(&mut self) -> Option<DeviceMenuMsg> {
|
||||||
if let Some(parent) = self.parent_subscreens.pop() {
|
if let Some(parent) = self.parent_subscreens.pop() {
|
||||||
self.active_subscreen = parent;
|
self.set_active_subscreen(parent);
|
||||||
self.place(self.bounds);
|
self.place(self.bounds);
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -380,7 +447,8 @@ 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(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),
|
Subscreen::AboutScreen => self.about_screen.place(bounds),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -393,7 +461,7 @@ impl<'a> Component for DeviceMenuScreen<'a> {
|
|||||||
|
|
||||||
// Handle the event for the active menu
|
// Handle the event for the active menu
|
||||||
match self.subscreens[self.active_subscreen] {
|
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)) => {
|
Some(VerticalMenuScreenMsg::Selected(index)) => {
|
||||||
return self.handle_submenu(ctx, 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) {
|
Subscreen::AboutScreen => match self.about_screen.event(ctx, event) {
|
||||||
Some(TextScreenMsg::Cancelled) => {
|
Some(TextScreenMsg::Cancelled) => {
|
||||||
return self.go_back();
|
return self.go_back();
|
||||||
@ -418,7 +500,8 @@ 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(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),
|
Subscreen::AboutScreen => self.about_screen.render(target),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,7 +783,7 @@ impl FirmwareUI for UIEckhart {
|
|||||||
Ok(layout)
|
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));
|
let layout = RootComponent::new(DeviceMenuScreen::new(failed_backup, battery_percentage, paired_devices));
|
||||||
Ok(layout)
|
Ok(layout)
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,7 @@ pub trait FirmwareUI {
|
|||||||
fn show_device_menu(
|
fn show_device_menu(
|
||||||
failed_backup: bool,
|
failed_backup: bool,
|
||||||
battery_percentage: usize,
|
battery_percentage: usize,
|
||||||
paired_devices: Vec<TString<'static>, 10>,
|
paired_devices: Vec<TString<'static>, 1>,
|
||||||
) -> Result<impl LayoutMaybeTrace, Error>;
|
) -> Result<impl LayoutMaybeTrace, Error>;
|
||||||
|
|
||||||
fn show_info(
|
fn show_info(
|
||||||
|
Loading…
Reference in New Issue
Block a user