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:
parent
73fc7a8a9f
commit
f045779d37
@ -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};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user