mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-24 13:22:05 +00:00
feat(eckhart): full-screen textual component
- TextScreen is a full-screen component for (paginated) texts - it's supposed to wrap FormattedText or Paragraphs
This commit is contained in:
parent
be4d6fa47c
commit
eb027d2ffa
@ -5,6 +5,7 @@ mod error;
|
|||||||
mod header;
|
mod header;
|
||||||
mod hint;
|
mod hint;
|
||||||
mod result;
|
mod result;
|
||||||
|
mod text_screen;
|
||||||
mod welcome_screen;
|
mod welcome_screen;
|
||||||
|
|
||||||
pub use action_bar::ActionBar;
|
pub use action_bar::ActionBar;
|
||||||
@ -13,6 +14,7 @@ pub use error::ErrorScreen;
|
|||||||
pub use header::{Header, HeaderMsg};
|
pub use header::{Header, HeaderMsg};
|
||||||
pub use hint::Hint;
|
pub use hint::Hint;
|
||||||
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
pub use result::{ResultFooter, ResultScreen, ResultStyle};
|
||||||
|
pub use text_screen::{AllowedTextContent, TextScreen, TextScreenMsg};
|
||||||
pub use welcome_screen::WelcomeScreen;
|
pub use welcome_screen::WelcomeScreen;
|
||||||
|
|
||||||
use super::{constant, theme};
|
use super::{constant, theme};
|
||||||
|
165
core/embed/rust/src/ui/layout_eckhart/component/text_screen.rs
Normal file
165
core/embed/rust/src/ui/layout_eckhart/component/text_screen.rs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
use crate::{
|
||||||
|
strutil::TString,
|
||||||
|
ui::{
|
||||||
|
component::{
|
||||||
|
swipe_detect::SwipeConfig,
|
||||||
|
text::paragraphs::{ParagraphSource, Paragraphs},
|
||||||
|
Component, Event, EventCtx, FormattedText, PaginateFull,
|
||||||
|
},
|
||||||
|
flow::Swipable,
|
||||||
|
geometry::{Insets, Rect},
|
||||||
|
shape::Renderer,
|
||||||
|
util::Pager,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{action_bar::ActionBarMsg, button::Button, ActionBar, Header, HeaderMsg, Hint};
|
||||||
|
|
||||||
|
/// Full-screen component for rendering text.
|
||||||
|
///
|
||||||
|
/// T should be either `Paragraphs` or `FormattedText`.
|
||||||
|
/// The component wraps the full content of the generic page spec:
|
||||||
|
/// - Header (Optional)
|
||||||
|
/// - Text
|
||||||
|
/// - Hint (Optional)
|
||||||
|
/// - Action bar (Optional)
|
||||||
|
pub struct TextScreen<T> {
|
||||||
|
header: Option<Header>,
|
||||||
|
content: T,
|
||||||
|
hint: Option<Hint<'static>>,
|
||||||
|
action_bar: Option<ActionBar>,
|
||||||
|
// TODO: swipe handling
|
||||||
|
// TODO: animations
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TextScreenMsg {
|
||||||
|
Cancelled,
|
||||||
|
Confirmed,
|
||||||
|
Menu,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TextScreen<T>
|
||||||
|
where
|
||||||
|
T: AllowedTextContent,
|
||||||
|
{
|
||||||
|
const CONTENT_INSETS: Insets = Insets::sides(24);
|
||||||
|
|
||||||
|
pub fn new(content: T) -> Self {
|
||||||
|
Self {
|
||||||
|
header: None,
|
||||||
|
content,
|
||||||
|
hint: None,
|
||||||
|
action_bar: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_header(mut self, header: Header) -> Self {
|
||||||
|
self.header = Some(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 = Some(action_bar);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_page(&mut self, page_idx: u16) {
|
||||||
|
self.content.change_page(page_idx);
|
||||||
|
let pager = self.content.pager();
|
||||||
|
self.hint.as_mut().map(|h| h.update(pager));
|
||||||
|
self.action_bar.as_mut().map(|ab| ab.update(pager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Component for TextScreen<T>
|
||||||
|
where
|
||||||
|
T: AllowedTextContent,
|
||||||
|
{
|
||||||
|
type Msg = TextScreenMsg;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let (rest, hint_area) = rest.split_bottom(hint.height());
|
||||||
|
hint.place(hint_area);
|
||||||
|
rest
|
||||||
|
} else {
|
||||||
|
rest
|
||||||
|
};
|
||||||
|
self.header.place(header_area);
|
||||||
|
self.content.place(content_area.inset(Self::CONTENT_INSETS));
|
||||||
|
self.action_bar.place(action_bar_area);
|
||||||
|
|
||||||
|
self.update_page(0);
|
||||||
|
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(TextScreenMsg::Cancelled),
|
||||||
|
HeaderMsg::Menu => return Some(TextScreenMsg::Menu),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(msg) = self.action_bar.event(ctx, event) {
|
||||||
|
match msg {
|
||||||
|
ActionBarMsg::Cancelled => return Some(TextScreenMsg::Cancelled),
|
||||||
|
ActionBarMsg::Confirmed => return Some(TextScreenMsg::Confirmed),
|
||||||
|
ActionBarMsg::Prev => {
|
||||||
|
self.update_page(self.content.pager().prev());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
ActionBarMsg::Next => {
|
||||||
|
self.update_page(self.content.pager().next());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
|
self.header.render(target);
|
||||||
|
self.content.render(target);
|
||||||
|
self.hint.render(target);
|
||||||
|
self.action_bar.render(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Swipable for TextScreen<T>
|
||||||
|
where
|
||||||
|
T: AllowedTextContent,
|
||||||
|
{
|
||||||
|
fn get_pager(&self) -> Pager {
|
||||||
|
self.content.pager()
|
||||||
|
}
|
||||||
|
fn get_swipe_config(&self) -> SwipeConfig {
|
||||||
|
SwipeConfig::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A marker trait used to constrain the allowed text content types in a
|
||||||
|
/// TextScreen.
|
||||||
|
pub trait AllowedTextContent: Component + PaginateFull {}
|
||||||
|
impl AllowedTextContent for FormattedText {}
|
||||||
|
impl<'a, T> AllowedTextContent for Paragraphs<T> where T: ParagraphSource<'a> {}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl<T> crate::trace::Trace for TextScreen<T>
|
||||||
|
where
|
||||||
|
T: AllowedTextContent + crate::trace::Trace,
|
||||||
|
{
|
||||||
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
|
t.component("TextComponent");
|
||||||
|
self.header.as_ref().map(|header| header.trace(t));
|
||||||
|
self.content.trace(t);
|
||||||
|
self.hint.as_ref().map(|hint| hint.trace(t));
|
||||||
|
self.action_bar.as_ref().map(|ab| ab.trace(t));
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,15 @@ use crate::{
|
|||||||
text::paragraphs::{ParagraphSource, Paragraphs},
|
text::paragraphs::{ParagraphSource, Paragraphs},
|
||||||
Component, Timeout,
|
Component, Timeout,
|
||||||
},
|
},
|
||||||
layout::{obj::ComponentMsgObj, result::CANCELLED},
|
layout::{
|
||||||
|
obj::ComponentMsgObj,
|
||||||
|
result::{CANCELLED, CONFIRMED, INFO},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::component::{AllowedTextContent, TextScreen, TextScreenMsg};
|
||||||
|
|
||||||
// Clippy/compiler complains about conflicting implementations
|
// Clippy/compiler complains about conflicting implementations
|
||||||
// TODO move the common impls to a common module
|
// TODO move the common impls to a common module
|
||||||
#[cfg(not(feature = "clippy"))]
|
#[cfg(not(feature = "clippy"))]
|
||||||
@ -32,3 +37,16 @@ where
|
|||||||
Ok(CANCELLED.as_obj())
|
Ok(CANCELLED.as_obj())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ComponentMsgObj for TextScreen<T>
|
||||||
|
where
|
||||||
|
T: AllowedTextContent,
|
||||||
|
{
|
||||||
|
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
|
||||||
|
match msg {
|
||||||
|
TextScreenMsg::Cancelled => Ok(CANCELLED.as_obj()),
|
||||||
|
TextScreenMsg::Confirmed => Ok(CONFIRMED.as_obj()),
|
||||||
|
TextScreenMsg::Menu => Ok(INFO.as_obj()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user