mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-23 21:02:23 +00:00
WIP: feat(eckhart): full-screen vertical menu component
This commit is contained in:
parent
3b0d997567
commit
a48061d36d
@ -6,6 +6,7 @@ mod header;
|
||||
mod hint;
|
||||
mod result;
|
||||
mod text_screen;
|
||||
mod vertical_menu_page;
|
||||
mod welcome_screen;
|
||||
|
||||
pub use action_bar::ActionBar;
|
||||
@ -15,6 +16,7 @@ pub use header::{Header, HeaderMsg};
|
||||
pub use hint::Hint;
|
||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
||||
pub use vertical_menu_page::VerticalMenuPage;
|
||||
pub use welcome_screen::WelcomeScreen;
|
||||
|
||||
use super::{constant, theme};
|
||||
|
@ -0,0 +1,133 @@
|
||||
use crate::{
|
||||
strutil::TString,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx},
|
||||
display::Icon,
|
||||
geometry::Rect,
|
||||
layout_eckhart::{component::button::IconText, theme},
|
||||
shape::Renderer,
|
||||
},
|
||||
};
|
||||
|
||||
use heapless::Vec;
|
||||
|
||||
use super::{button::ButtonMsg, constant, Button, Header, HeaderMsg};
|
||||
|
||||
/// Number of buttons.
|
||||
/// Presently, VerticalMenu holds only fixed number of buttons.
|
||||
const MENU_MAX_ITEMS: usize = 4;
|
||||
|
||||
type VerticalMenuButtons = Vec<Button, MENU_MAX_ITEMS>;
|
||||
|
||||
/// TODO: this is just a mockup for now
|
||||
pub struct VerticalMenuPage {
|
||||
header: Header,
|
||||
buttons: VerticalMenuButtons,
|
||||
}
|
||||
|
||||
pub enum VerticalMenuMsg {
|
||||
Selected(usize),
|
||||
/// Left header button clicked
|
||||
Back,
|
||||
/// Right header button clicked
|
||||
Close,
|
||||
}
|
||||
|
||||
impl VerticalMenuPage {
|
||||
fn new(buttons: VerticalMenuButtons) -> Self {
|
||||
Self {
|
||||
header: Header::new(TString::empty()),
|
||||
buttons,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self::new(VerticalMenuButtons::new())
|
||||
}
|
||||
|
||||
pub fn with_header(mut self, header: Header) -> Self {
|
||||
self.header = header;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn item(mut self, icon: Icon, text: TString<'static>) -> Self {
|
||||
unwrap!(self.buttons.push(
|
||||
Button::with_icon_and_text(IconText::new(text, icon)).styled(theme::button_default())
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn danger(mut self, icon: Icon, text: TString<'static>) -> Self {
|
||||
unwrap!(
|
||||
(self.buttons.push(
|
||||
Button::with_icon_and_text(IconText::new(text, icon))
|
||||
.styled(theme::button_warning_high())
|
||||
)),
|
||||
"unwrap failed"
|
||||
);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for VerticalMenuPage {
|
||||
type Msg = VerticalMenuMsg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
// assert full screen
|
||||
debug_assert_eq!(bounds.height(), constant::HEIGHT);
|
||||
debug_assert_eq!(bounds.width(), constant::WIDTH);
|
||||
|
||||
const MENU_BUTTON_HEIGHT: i16 = 64; // TODO: variable height buttons
|
||||
/// Fixed height of a separator.
|
||||
const MENU_SEP_HEIGHT: i16 = 2;
|
||||
let n_seps = self.buttons.len() - 1;
|
||||
let (header_area, mut rest) = bounds.split_top(Header::HEADER_HEIGHT);
|
||||
for (i, button) in self.buttons.iter_mut().enumerate() {
|
||||
let (area_button, new_remaining) = rest.split_top(MENU_BUTTON_HEIGHT);
|
||||
button.place(area_button);
|
||||
rest = new_remaining;
|
||||
if i < n_seps {
|
||||
let (_area_sep, new_remaining) = rest.split_top(MENU_SEP_HEIGHT);
|
||||
rest = new_remaining;
|
||||
}
|
||||
}
|
||||
|
||||
self.header.place(header_area);
|
||||
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
if let Some(msg) = self.header.event(ctx, event) {
|
||||
match msg {
|
||||
HeaderMsg::Cancelled => return Some(VerticalMenuMsg::Close),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, button) in self.buttons.iter_mut().enumerate() {
|
||||
if let Some(ButtonMsg::Clicked) = button.event(ctx, event) {
|
||||
return Some(VerticalMenuMsg::Selected(i));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
for (i, button) in (&self.buttons).into_iter().enumerate() {
|
||||
button.render(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for VerticalMenuPage {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("VerticalMenuPage");
|
||||
t.in_list("buttons", &|button_list| {
|
||||
for button in &self.buttons {
|
||||
button_list.child(button);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -200,6 +200,31 @@ pub const fn button_confirm() -> ButtonStyleSheet {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn button_warning_high() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
normal: &ButtonStyle {
|
||||
font: fonts::FONT_SATOSHI_MEDIUM_26,
|
||||
text_color: ORANGE,
|
||||
button_color: BG,
|
||||
icon_color: ORANGE,
|
||||
background_color: BG,
|
||||
},
|
||||
active: &ButtonStyle {
|
||||
font: fonts::FONT_SATOSHI_MEDIUM_26,
|
||||
text_color: ORANGE,
|
||||
button_color: GREY_SUPER_DARK,
|
||||
icon_color: GREEN,
|
||||
background_color: BG,
|
||||
},
|
||||
disabled: &ButtonStyle {
|
||||
font: fonts::FONT_SATOSHI_MEDIUM_26,
|
||||
text_color: ORANGE,
|
||||
button_color: GREY_DARK,
|
||||
icon_color: GREY_LIGHT,
|
||||
background_color: GREY_DARK,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn button_header() -> ButtonStyleSheet {
|
||||
ButtonStyleSheet {
|
||||
|
Loading…
Reference in New Issue
Block a user