1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-22 20:42:03 +00:00

feat(eckhart): full-screen select word component

This commit is contained in:
Lukas Bielesch 2025-02-04 12:16:57 +01:00
parent ee1a3112c9
commit b84a983678
4 changed files with 133 additions and 6 deletions

View File

@ -5,6 +5,7 @@ mod error;
mod header;
mod hint;
mod result;
mod select_word_screen;
mod share_words;
mod text_screen;
mod vertical_menu;
@ -17,6 +18,7 @@ pub use error::ErrorScreen;
pub use header::{Header, HeaderMsg};
pub use hint::Hint;
pub use result::{ResultFooter, ResultScreen, ResultStyle};
pub use select_word_screen::{SelectWordMsg, SelectWordScreen};
#[cfg(feature = "translations")]
pub use share_words::{ShareWordsScreen, ShareWordsScreenMsg};
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};

View File

@ -0,0 +1,107 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, Label},
geometry::{Alignment, Insets, Rect},
layout_eckhart::{
component::{Button, Header, HeaderMsg, VerticalMenu, VerticalMenuMsg},
constant::SCREEN,
theme,
},
shape::Renderer,
ui_firmware::MAX_WORD_QUIZ_ITEMS,
},
};
pub struct SelectWordScreen {
header: Header,
description: Label<'static>,
menu: VerticalMenu,
}
pub enum SelectWordMsg {
Selected(usize),
/// Right header button clicked
Cancelled,
}
impl SelectWordScreen {
const INSET: i16 = 24;
const DESCRIPTION_HEIGHT: i16 = 52;
const BUTTON_RADIUS: u8 = 12;
pub fn new(
share_words_vec: [TString<'static>; MAX_WORD_QUIZ_ITEMS],
description: TString<'static>,
) -> Self {
let mut menu = VerticalMenu::empty().with_separators().with_fit_area();
for word in share_words_vec {
menu = menu.item(
Button::with_text(word)
.styled(theme::button_select_word())
.with_radius(Self::BUTTON_RADIUS),
);
}
Self {
header: Header::new(TString::empty()),
description: Label::new(description, Alignment::Start, theme::TEXT_MEDIUM)
.vertically_centered(),
menu,
}
}
pub fn with_header(mut self, header: Header) -> Self {
self.header = header;
self
}
}
impl Component for SelectWordScreen {
type Msg = SelectWordMsg;
fn place(&mut self, bounds: Rect) -> Rect {
// assert full screen
debug_assert_eq!(bounds.height(), SCREEN.height());
debug_assert_eq!(bounds.width(), SCREEN.width());
let (header_area, rest) = bounds.split_top(Header::HEADER_HEIGHT);
let (description_area, rest) = rest.split_top(Self::DESCRIPTION_HEIGHT);
let (_, rest) = rest.split_top(Self::INSET);
let (menu_area, _) = rest.split_bottom(Self::INSET);
let description_area = description_area.inset(Insets::sides(Self::INSET));
self.menu.place(menu_area);
self.description.place(description_area);
self.header.place(header_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(HeaderMsg::Cancelled) = self.header.event(ctx, event) {
return Some(SelectWordMsg::Cancelled);
}
if let Some(VerticalMenuMsg::Selected(i)) = self.menu.event(ctx, event) {
return Some(SelectWordMsg::Selected(i));
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.header.render(target);
self.description.render(target);
self.menu.render(target);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for SelectWordScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("SelectWordScreen");
}
}

View File

@ -13,7 +13,9 @@ use crate::{
},
};
use super::component::{AllowedTextContent, TextScreen, TextScreenMsg};
use super::component::{
AllowedTextContent, SelectWordMsg, SelectWordScreen, TextScreen, TextScreenMsg,
};
// Clippy/compiler complains about conflicting implementations
// TODO move the common impls to a common module
@ -50,3 +52,12 @@ where
}
}
}
impl ComponentMsgObj for SelectWordScreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
SelectWordMsg::Selected(i) => i.try_into(),
SelectWordMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}

View File

@ -24,7 +24,7 @@ use crate::{
};
use super::{
component::{ActionBar, Button, Header, HeaderMsg, Hint, TextScreen},
component::{ActionBar, Button, Header, HeaderMsg, Hint, SelectWordScreen, TextScreen},
flow, fonts, theme, UIEckhart,
};
@ -389,11 +389,18 @@ impl FirmwareUI for UIEckhart {
}
fn select_word(
_title: TString<'static>,
_description: TString<'static>,
_words: [TString<'static>; MAX_WORD_QUIZ_ITEMS],
title: TString<'static>,
description: TString<'static>,
words: [TString<'static>; MAX_WORD_QUIZ_ITEMS],
) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
let component = SelectWordScreen::new(words, description).with_header(
Header::new(title)
.with_right_button(Button::with_icon(theme::ICON_MENU), HeaderMsg::Cancelled),
);
let layout = RootComponent::new(component);
Ok(layout)
}
fn select_word_count(_recovery_type: RecoveryType) -> Result<impl LayoutMaybeTrace, Error> {