From f045779d37b14d9e3fc1e3e3d75e89d249e2eec7 Mon Sep 17 00:00:00 2001 From: obrusvit Date: Sat, 11 Jan 2025 17:22:08 +0100 Subject: [PATCH] WIP: feat(jefferson): high-level textual component --- .../src/ui/layout_jefferson/component/mod.rs | 2 + .../component/text_component.rs | 131 ++++++++++++++++++ .../ui/layout_jefferson/component_msg_obj.rs | 9 ++ 3 files changed, 142 insertions(+) create mode 100644 core/embed/rust/src/ui/layout_jefferson/component/text_component.rs diff --git a/core/embed/rust/src/ui/layout_jefferson/component/mod.rs b/core/embed/rust/src/ui/layout_jefferson/component/mod.rs index b9e96051d1..c9c57c8d36 100644 --- a/core/embed/rust/src/ui/layout_jefferson/component/mod.rs +++ b/core/embed/rust/src/ui/layout_jefferson/component/mod.rs @@ -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}; diff --git a/core/embed/rust/src/ui/layout_jefferson/component/text_component.rs b/core/embed/rust/src/ui/layout_jefferson/component/text_component.rs new file mode 100644 index 0000000000..7f8026e4b2 --- /dev/null +++ b/core/embed/rust/src/ui/layout_jefferson/component/text_component.rs @@ -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>, + 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 { + 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); + } +} diff --git a/core/embed/rust/src/ui/layout_jefferson/component_msg_obj.rs b/core/embed/rust/src/ui/layout_jefferson/component_msg_obj.rs index 7ddfbedc87..af063a36db 100644 --- a/core/embed/rust/src/ui/layout_jefferson/component_msg_obj.rs +++ b/core/embed/rust/src/ui/layout_jefferson/component_msg_obj.rs @@ -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 { + msg.try_into() + } +}