You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/ui/model_tr/bootloader/menu.rs

197 lines
5.0 KiB

#[cfg(feature = "ui_debug")]
use crate::trace::{Trace, Tracer};
use crate::{
trezorhal::secbool::{secbool, sectrue},
ui::{
component::{Child, Component, Event, EventCtx, Pad},
constant::screen,
display,
display::{Font, Icon},
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
layout::simplified::ReturnToC,
shape,
shape::Renderer,
},
};
use super::super::{
component::{ButtonLayout, Choice, ChoiceFactory, ChoicePage},
theme::bootloader::{BLD_BG, BLD_FG, ICON_EXIT, ICON_REDO, ICON_TRASH},
};
#[repr(u32)]
#[derive(Copy, Clone)]
pub enum MenuMsg {
Close = 0xAABBCCDD,
Reboot = 0x11223344,
FactoryReset = 0x55667788,
}
impl ReturnToC for MenuMsg {
fn return_to_c(self) -> u32 {
self as u32
}
}
const CHOICE_LENGTH: usize = 3;
const SCREEN_CENTER: Point = screen().center();
pub struct MenuChoice {
first_line: &'static str,
second_line: &'static str,
icon: Icon,
}
impl MenuChoice {
pub fn new(first_line: &'static str, second_line: &'static str, icon: Icon) -> Self {
Self {
first_line,
second_line,
icon,
}
}
}
impl Choice for MenuChoice {
fn paint_center(&self, _area: Rect, _inverse: bool) {
// Icon on top and two lines of text below
self.icon.draw(
SCREEN_CENTER + Offset::y(-20),
Alignment2D::CENTER,
BLD_FG,
BLD_BG,
);
display::text_center(SCREEN_CENTER, self.first_line, Font::NORMAL, BLD_FG, BLD_BG);
display::text_center(
SCREEN_CENTER + Offset::y(10),
self.second_line,
Font::NORMAL,
BLD_FG,
BLD_BG,
);
}
fn render_center<'s>(&self, target: &mut impl Renderer<'s>, _area: Rect, _inverse: bool) {
// Icon on top and two lines of text below
shape::ToifImage::new(SCREEN_CENTER + Offset::y(-20), self.icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(BLD_FG)
.render(target);
shape::Text::new(SCREEN_CENTER, self.first_line)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
shape::Text::new(SCREEN_CENTER + Offset::y(10), self.second_line)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
fn btn_layout(&self) -> ButtonLayout {
ButtonLayout::arrow_armed_arrow("SELECT".into())
}
}
#[cfg(feature = "ui_debug")]
impl Trace for MenuChoice {
fn trace(&self, t: &mut dyn Tracer) {
t.component("MenuChoice");
}
}
pub struct MenuChoiceFactory {
firmware_present: secbool,
}
impl MenuChoiceFactory {
const CHOICES: [(&'static str, &'static str, Icon); CHOICE_LENGTH] = [
("Factory", "reset", ICON_TRASH),
("Exit", "menu", ICON_EXIT),
("Reboot", "Trezor", ICON_REDO),
];
pub fn new(firmware_present: secbool) -> Self {
Self { firmware_present }
}
}
impl ChoiceFactory for MenuChoiceFactory {
type Action = MenuMsg;
type Item = MenuChoice;
fn count(&self) -> usize {
if self.firmware_present == sectrue {
CHOICE_LENGTH
} else {
CHOICE_LENGTH - 1
}
}
fn get(&self, choice_index: usize) -> (Self::Item, Self::Action) {
let choice_item = MenuChoice::new(
Self::CHOICES[choice_index].0,
Self::CHOICES[choice_index].1,
Self::CHOICES[choice_index].2,
);
let action = match choice_index {
0 => MenuMsg::FactoryReset,
1 => MenuMsg::Close,
2 if self.firmware_present == sectrue => MenuMsg::Reboot,
_ => unreachable!(),
};
(choice_item, action)
}
}
pub struct Menu {
pad: Pad,
choice_page: Child<ChoicePage<MenuChoiceFactory, MenuMsg>>,
}
impl Menu {
pub fn new(firmware_present: secbool) -> Self {
let choices = MenuChoiceFactory::new(firmware_present);
Self {
pad: Pad::with_background(BLD_BG).with_clear(),
choice_page: Child::new(
ChoicePage::new(choices)
.with_carousel(true)
.with_only_one_item(true),
),
}
}
}
impl Component for Menu {
type Msg = MenuMsg;
fn place(&mut self, bounds: Rect) -> Rect {
self.pad.place(bounds);
self.choice_page.place(bounds);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
self.choice_page.event(ctx, event).map(|evt| evt.0)
}
fn paint(&mut self) {
self.pad.paint();
self.choice_page.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
self.choice_page.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.choice_page.bounds(sink)
}
}