1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-27 07:40:59 +00:00

WIP: feat(jefferson): high-level textual component

This commit is contained in:
obrusvit 2025-01-11 17:22:08 +01:00
parent 73fc7a8a9f
commit f045779d37
3 changed files with 142 additions and 0 deletions

View File

@ -4,6 +4,7 @@ mod error;
mod header;
mod hint;
mod result;
mod text_component;
mod welcome_screen;
pub use action_bar::ActionBar;
@ -12,6 +13,7 @@ pub use error::ErrorScreen;
pub use header::Header;
pub use hint::Hint;
pub use result::{ResultFooter, ResultScreen, ResultStyle};
pub use text_component::GenericScreen;
pub use welcome_screen::WelcomeScreen;
use super::{constant, theme};

View File

@ -0,0 +1,131 @@
use crate::{
strutil::TString,
ui::{
component::{Component, Event, EventCtx, FormattedText, Paginate},
flow::FlowMsg,
geometry::Rect,
shape::Renderer,
},
};
use super::{action_bar::ActionBarMsg, button::Button, ActionBar, Header, Hint};
/// High-level component for rendering formatted text, possibly paginated. The
/// component wraps the full content of the generic screen spec:
/// - Header
/// - Text
/// - Hint (Optional)
/// - Action bar
pub struct GenericScreen {
header: Header,
content: FormattedText,
hint: Option<Hint<'static>>,
action_bar: ActionBar,
/// Current index of the paginated `content`
page_idx: usize,
/// Max pages of the paginated `content`, computed in `place`
page_count: usize,
// TODO: swipe handling
// TODO: animations
}
impl GenericScreen {
pub fn new(content: FormattedText) -> Self {
Self {
header: Header::new(TString::empty()),
content,
hint: None,
action_bar: ActionBar::new_single(Button::empty()),
page_idx: 0,
page_count: 1,
}
}
pub fn with_header(mut self, header: Header) -> Self {
self.header = header;
self
}
pub fn with_hint(mut self, hint: Hint<'static>) -> Self {
self.hint = Some(hint);
self
}
pub fn with_action_bar(mut self, action_bar: ActionBar) -> Self {
self.action_bar = action_bar;
self
}
fn update_page(&mut self) {
self.content.change_page(self.page_idx);
if let Some(hint) = &mut self.hint {
hint.update_page(self.page_idx, self.page_count);
}
self.action_bar.update_page(self.page_idx, self.page_count);
}
}
impl Component for GenericScreen {
type Msg = FlowMsg;
fn place(&mut self, bounds: Rect) -> Rect {
let (header_area, rest) = bounds.split_top(Header::HEADER_HEIGHT);
let (rest, action_bar_area) = rest.split_bottom(ActionBar::ACTION_BAR_HEIGHT);
let content_area = if let Some(hint) = &mut self.hint {
// TODO: hint area based on text
let (rest, hint_area) = rest.split_bottom(Hint::SINGLE_LINE_HEIGHT);
hint.place(hint_area);
rest
} else {
rest
};
self.header.place(header_area);
self.content.place(content_area);
self.action_bar.place(action_bar_area);
// after computing the layout, update number of pages
self.page_count = self.content.page_count();
self.update_page();
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
let msg = self.action_bar.event(ctx, event);
match msg {
Some(ActionBarMsg::Cancelled) => Some(FlowMsg::Cancelled),
Some(ActionBarMsg::Confirmed) => Some(FlowMsg::Confirmed),
Some(ActionBarMsg::Prev) => {
self.page_idx = (self.page_idx - 1).max(0);
self.update_page();
None
}
Some(ActionBarMsg::Next) => {
self.page_idx = (self.page_idx + 1).min(self.page_count - 1);
self.update_page();
None
}
_ => None,
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.header.render(target);
self.content.render(target);
if let Some(hint) = &self.hint {
hint.render(target);
}
self.action_bar.render(target);
}
}
impl crate::trace::Trace for GenericScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("TextComponent");
self.header.trace(t);
self.content.trace(t);
if let Some(hint) = &self.hint {
hint.trace(t);
}
self.action_bar.trace(t);
}
}

View File

@ -3,6 +3,7 @@ use crate::{
micropython::obj::Obj,
ui::{
component::{
base,
text::paragraphs::{ParagraphSource, Paragraphs},
Component, Timeout,
},
@ -10,6 +11,8 @@ use crate::{
},
};
use super::component::GenericScreen;
// Clippy/compiler complains about conflicting implementations
// TODO move the common impls to a common module
#[cfg(not(feature = "clippy"))]
@ -32,3 +35,9 @@ where
Ok(CANCELLED.as_obj())
}
}
impl ComponentMsgObj for GenericScreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
msg.try_into()
}
}