diff --git a/core/embed/rust/src/ui/component/mod.rs b/core/embed/rust/src/ui/component/mod.rs index 10a92b09e..928e9817c 100644 --- a/core/embed/rust/src/ui/component/mod.rs +++ b/core/embed/rust/src/ui/component/mod.rs @@ -13,7 +13,7 @@ pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToke pub use empty::Empty; pub use label::{Label, LabelStyle}; pub use pad::Pad; -pub use paginated::{Paginate, Paginated, PaginatedMsg}; +pub use paginated::{PageMsg, Paginate}; pub use text::{ formatted::FormattedText, layout::{LineBreaking, PageBreaking, TextLayout}, diff --git a/core/embed/rust/src/ui/component/paginated.rs b/core/embed/rust/src/ui/component/paginated.rs index 8ae6aabfd..93715e077 100644 --- a/core/embed/rust/src/ui/component/paginated.rs +++ b/core/embed/rust/src/ui/component/paginated.rs @@ -1,113 +1,18 @@ -use crate::ui::{ - component::{ - text::layout::{LayoutFit, TextNoOp}, - Component, ComponentExt, Event, EventCtx, FormattedText, Pad, - }, - display::Color, - geometry::Rect, +use crate::ui::component::{ + text::layout::{LayoutFit, TextNoOp}, + FormattedText, }; -/// Implementations of `Page` wrap the component being paged. They also contain -/// model-dependent logic like: -/// -/// * rendering scrollbar -/// * detecting swipe on TT -/// * buttons for changing pages on T1 -/// * fading backlight -pub trait Page { - type Content; - fn new(area: Rect, page: Self::Content, page_count: usize, active_page: usize) -> Self; - fn inner_mut(&mut self) -> &mut Self::Content; - fn page_count(&self) -> usize; - fn active_page(&self) -> usize; - fn fade_after_next_paint(&mut self); - fn content_area(area: Rect) -> Rect; -} - -/// Implementation of `Page` is a `Component` returning this message. +/// Common message type for pagination components. pub enum PageMsg { /// Pass-through from paged component. Content(T), - /// Pass-through from other `Component`s. - Controls(U), - - /// Page change requested. - ChangePage(usize), -} - -/// Handles page redraw on `ChangePage` message, and other model-agnostic logic. -pub struct Paginated

{ - page: P, - pad: Pad, -} - -pub enum PaginatedMsg { - /// Pass-through from the paged `Component`. - Content(T), - /// Messages from page controls outside the paged component. Currently only /// used on T1 for "OK" and "Cancel" buttons. Controls(U), } -impl

Paginated

-where - P: Page, - P::Content: Paginate, -{ - pub fn new(area: Rect, content: impl FnOnce(Rect) -> P::Content, background: Color) -> Self { - let active_page = 0; - let mut content = content(P::content_area(area)); - let page_count = content.page_count(); - Self { - page: P::new(area, content, page_count, active_page), - pad: Pad::with_background(area, background), - } - } -} - -// C is type of message returned by page controls. -impl Component for Paginated

-where - P: Page, - P: Component::Content as Component>::Msg, C>>, - P::Content: Paginate, - P::Content: Component, -{ - type Msg = PaginatedMsg<<

::Content as Component>::Msg, C>; - - fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - self.page.event(ctx, event).and_then(|msg| match msg { - PageMsg::Content(c) => Some(PaginatedMsg::Content(c)), - PageMsg::Controls(c) => Some(PaginatedMsg::Controls(c)), - PageMsg::ChangePage(page) => { - self.page.fade_after_next_paint(); - self.page.inner_mut().change_page(page); - self.page.inner_mut().request_complete_repaint(ctx); - self.pad.clear(); - None - } - }) - } - - fn paint(&mut self) { - self.pad.paint(); - self.page.paint(); - } -} - -#[cfg(feature = "ui_debug")] -impl

crate::trace::Trace for Paginated

-where - P: Page + crate::trace::Trace, - P::Content: crate::trace::Trace, -{ - fn trace(&self, t: &mut dyn crate::trace::Tracer) { - self.page.trace(t); - } -} - pub trait Paginate { fn page_count(&mut self) -> usize; fn change_page(&mut self, active_page: usize); diff --git a/core/embed/rust/src/ui/component/text/paragraphs.rs b/core/embed/rust/src/ui/component/text/paragraphs.rs index fb8386d75..d0ab6186f 100644 --- a/core/embed/rust/src/ui/component/text/paragraphs.rs +++ b/core/embed/rust/src/ui/component/text/paragraphs.rs @@ -38,7 +38,7 @@ where } pub fn add(mut self, text_font: Font, content: T) -> Self { - if content.as_ref().len() == 0 { + if content.as_ref().is_empty() { return self; } let paragraph = Paragraph::new( diff --git a/core/embed/rust/src/ui/model_t1/component/page.rs b/core/embed/rust/src/ui/model_t1/component/page.rs index bd8446123..2f06163db 100644 --- a/core/embed/rust/src/ui/model_t1/component/page.rs +++ b/core/embed/rust/src/ui/model_t1/component/page.rs @@ -1,39 +1,35 @@ use crate::ui::{ - component::{ - paginated::{Page, PageMsg}, - Component, Event, EventCtx, Never, - }, - display, + component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate}, + display::{self, Color}, geometry::{Offset, Point, Rect}, }; use super::{theme, Button, ButtonMsg, ButtonPos}; pub struct ButtonPage { + content: T, scrollbar: ScrollBar, + pad: Pad, prev: Button<&'static str>, next: Button<&'static str>, cancel: Button<&'static str>, confirm: Button<&'static str>, - page: T, } -impl ButtonPage { - fn areas(area: Rect) -> (Rect, Rect, Rect) { - let button_height = theme::FONT_BOLD.line_height() + 2; - let (content_area, button_area) = area.hsplit(-button_height); - let (content_area, scrollbar_area) = content_area.vsplit(-ScrollBar::WIDTH); - let (content_area, _) = content_area.hsplit(-1); - (content_area, scrollbar_area, button_area) - } -} +impl ButtonPage +where + T: Paginate, + T: Component, +{ + pub fn new(area: Rect, content: impl FnOnce(Rect) -> T, background: Color) -> Self { + let (content_area, scrollbar_area, button_area) = Self::areas(area); + let mut content = content(content_area); + let pad = Pad::with_background(area, background); -impl Page for ButtonPage { - type Content = T; + // Always start at the first page. + let scrollbar = ScrollBar::vertical_right(scrollbar_area, content.page_count(), 0); - fn new(area: Rect, page: T, page_count: usize, active_page: usize) -> Self { - let (_content_area, scrollbar_area, button_area) = Self::areas(area); - let scrollbar = ScrollBar::vertical_right(scrollbar_area, page_count, active_page); + // Create the button controls. let prev = Button::with_text(button_area, ButtonPos::Left, "BACK", theme::button_cancel()); let next = Button::with_text( button_area, @@ -53,36 +49,40 @@ impl Page for ButtonPage { "CONFIRM", theme::button_default(), ); + Self { + content, scrollbar, + pad, prev, next, cancel, confirm, - page, } } - fn inner_mut(&mut self) -> &mut T { - &mut self.page - } - - fn page_count(&self) -> usize { - self.scrollbar.page_count - } - - fn active_page(&self) -> usize { - self.scrollbar.active_page + fn areas(area: Rect) -> (Rect, Rect, Rect) { + let button_height = theme::FONT_BOLD.line_height() + 2; + let (content_area, button_area) = area.hsplit(-button_height); + let (content_area, scrollbar_area) = content_area.vsplit(-ScrollBar::WIDTH); + let (content_area, _) = content_area.hsplit(-1); + (content_area, scrollbar_area, button_area) } - fn fade_after_next_paint(&mut self) {} - - fn content_area(area: Rect) -> Rect { - Self::areas(area).0 + fn change_page(&mut self, ctx: &mut EventCtx, page: usize) { + // Change the page in the content, clear the background under it and make sure + // it gets completely repainted. + self.content.change_page(page); + self.content.request_complete_repaint(ctx); + self.pad.clear(); } } -impl Component for ButtonPage { +impl Component for ButtonPage +where + T: Component, + T: Paginate, +{ type Msg = PageMsg; fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { @@ -90,34 +90,33 @@ impl Component for ButtonPage { if let Some(ButtonMsg::Clicked) = self.prev.event(ctx, event) { // Scroll up. self.scrollbar.go_to_previous_page(); - return Some(PageMsg::ChangePage(self.active_page())); - } - } else { - if let Some(ButtonMsg::Clicked) = self.cancel.event(ctx, event) { - return Some(PageMsg::Controls(false)); + self.change_page(ctx, self.scrollbar.active_page); + return None; } + } else if let Some(ButtonMsg::Clicked) = self.cancel.event(ctx, event) { + return Some(PageMsg::Controls(false)); } if self.scrollbar.has_next_page() { if let Some(ButtonMsg::Clicked) = self.next.event(ctx, event) { // Scroll down. self.scrollbar.go_to_next_page(); - return Some(PageMsg::ChangePage(self.active_page())); - } - } else { - if let Some(ButtonMsg::Clicked) = self.confirm.event(ctx, event) { - return Some(PageMsg::Controls(true)); + self.change_page(ctx, self.scrollbar.active_page); + return None; } + } else if let Some(ButtonMsg::Clicked) = self.confirm.event(ctx, event) { + return Some(PageMsg::Controls(true)); } - if let Some(msg) = self.page.event(ctx, event) { + if let Some(msg) = self.content.event(ctx, event) { return Some(PageMsg::Content(msg)); } None } fn paint(&mut self) { - self.page.paint(); + self.pad.paint(); + self.content.paint(); self.scrollbar.paint(); if self.scrollbar.has_previous_page() { self.prev.paint(); @@ -139,9 +138,9 @@ where { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("ButtonPage"); - t.field("active_page", &self.active_page()); - t.field("page_count", &self.page_count()); - t.field("content", &self.page); + t.field("active_page", &self.scrollbar.active_page); + t.field("page_count", &self.scrollbar.page_count); + t.field("content", &self.content); t.close(); } } diff --git a/core/embed/rust/src/ui/model_t1/layout.rs b/core/embed/rust/src/ui/model_t1/layout.rs index 810b823f0..535ca1913 100644 --- a/core/embed/rust/src/ui/model_t1/layout.rs +++ b/core/embed/rust/src/ui/model_t1/layout.rs @@ -4,7 +4,7 @@ use crate::{ error::Error, micropython::{buffer::Buffer, map::Map, obj::Obj, qstr::Qstr}, ui::{ - component::{text::paragraphs::Paragraphs, Child, FormattedText, Paginated, PaginatedMsg}, + component::{text::paragraphs::Paragraphs, Child, FormattedText, PageMsg}, display, layout::obj::LayoutObj, }, @@ -16,13 +16,13 @@ use super::{ theme, }; -impl TryFrom> for Obj { +impl TryFrom> for Obj { type Error = Error; - fn try_from(val: PaginatedMsg) -> Result { + fn try_from(val: PageMsg) -> Result { match val { - PaginatedMsg::Content(_) => 2.try_into(), - PaginatedMsg::Controls(c) => Ok(c.into()), + PageMsg::Content(_) => 2.try_into(), + PageMsg::Controls(c) => Ok(c.into()), } } } @@ -57,7 +57,7 @@ extern "C" fn ui_layout_new_confirm_action( .map(|label| |area, pos| Button::with_text(area, pos, label, theme::button_default())); let obj = LayoutObj::new(Child::new(Title::new(display::screen(), title, |area| { - Paginated::>::new( + ButtonPage::new( area, |area| { FormattedText::new::(area, format) @@ -85,7 +85,7 @@ extern "C" fn ui_layout_new_confirm_text( kwargs.get(Qstr::MP_QSTR_description)?.try_into_option()?; let obj = LayoutObj::new(Child::new(Title::new(display::screen(), title, |area| { - Paginated::>::new( + ButtonPage::new( area, |area| { Paragraphs::new(area) diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index f4834d6c0..48cc69cd0 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -1,65 +1,70 @@ use crate::ui::{ - component::{ - paginated::{Page, PageMsg}, - Component, Event, EventCtx, Never, - }, - display, + component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate}, + display::{self, Color}, geometry::{Offset, Point, Rect}, }; use super::{theme, Swipe, SwipeDirection}; pub struct SwipePage { + content: T, + pad: Pad, swipe: Swipe, scrollbar: ScrollBar, - page: T, fade: Option, } -impl SwipePage { - fn setup_swipe(scrollbar: &ScrollBar, swipe: &mut Swipe) { - swipe.allow_up = scrollbar.has_next_page(); - swipe.allow_down = scrollbar.has_previous_page(); - } -} +impl SwipePage +where + T: Paginate, + T: Component, +{ + pub fn new(area: Rect, content: impl FnOnce(Rect) -> T, background: Color) -> Self { + // Content occupies the whole area. + let mut content = content(area); -impl Page for SwipePage { - type Content = T; + // Always start at the first page. + let scrollbar = ScrollBar::vertical_right(area, content.page_count(), 0); - fn new(area: Rect, page: T, page_count: usize, active_page: usize) -> Self { - let scrollbar = ScrollBar::vertical_right(area, page_count, active_page); - let mut swipe = Swipe::new(area); - Self::setup_swipe(&scrollbar, &mut swipe); + let swipe = Self::make_swipe(area, &scrollbar); + let pad = Pad::with_background(area, background); Self { - swipe, + content, scrollbar, - page, + swipe, + pad, fade: None, } } - fn inner_mut(&mut self) -> &mut T { - &mut self.page + fn make_swipe(area: Rect, scrollbar: &ScrollBar) -> Swipe { + let mut swipe = Swipe::new(area); + swipe.allow_up = scrollbar.has_next_page(); + swipe.allow_down = scrollbar.has_previous_page(); + swipe } - fn page_count(&self) -> usize { - self.scrollbar.page_count - } + fn change_page(&mut self, ctx: &mut EventCtx, page: usize) { + // Adjust the swipe parameters. + self.swipe = Self::make_swipe(self.swipe.area, &self.scrollbar); - fn active_page(&self) -> usize { - self.scrollbar.active_page - } + // Change the page in the content, make sure it gets completely repainted and + // clear the background under it. + self.content.change_page(page); + self.content.request_complete_repaint(ctx); + self.pad.clear(); - fn fade_after_next_paint(&mut self) { + // Swipe has dimmed the screen, so fade back to normal backlight after the next + // paint. self.fade = Some(theme::BACKLIGHT_NORMAL); } - - fn content_area(area: Rect) -> Rect { - area - } } -impl Component for SwipePage { +impl Component for SwipePage +where + T: Paginate, + T: Component, +{ type Msg = PageMsg; fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { @@ -68,30 +73,32 @@ impl Component for SwipePage { SwipeDirection::Up => { // Scroll down, if possible. self.scrollbar.go_to_next_page(); - Self::setup_swipe(&self.scrollbar, &mut self.swipe); - return Some(PageMsg::ChangePage(self.active_page())); + self.change_page(ctx, self.scrollbar.active_page); + return None; } SwipeDirection::Down => { // Scroll up, if possible. self.scrollbar.go_to_previous_page(); - Self::setup_swipe(&self.scrollbar, &mut self.swipe); - return Some(PageMsg::ChangePage(self.active_page())); + self.change_page(ctx, self.scrollbar.active_page); + return None; } _ => { // Ignore other directions. } } } - if let Some(msg) = self.page.event(ctx, event) { + if let Some(msg) = self.content.event(ctx, event) { return Some(PageMsg::Content(msg)); } None } fn paint(&mut self) { - self.page.paint(); + self.pad.paint(); + self.content.paint(); self.scrollbar.paint(); if let Some(val) = self.fade.take() { + // Note that this is blocking and takes some time. display::fade_backlight(val); } } @@ -104,9 +111,9 @@ where { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("SwipePage"); - t.field("active_page", &self.active_page()); - t.field("page_count", &self.page_count()); - t.field("content", &self.page); + t.field("active_page", &self.scrollbar.active_page); + t.field("page_count", &self.scrollbar.page_count); + t.field("content", &self.content); t.close(); } } @@ -138,11 +145,15 @@ impl ScrollBar { } pub fn go_to_next_page(&mut self) { - self.active_page = self.active_page.saturating_add(1).min(self.page_count - 1); + self.go_to(self.active_page.saturating_add(1).min(self.page_count - 1)); } pub fn go_to_previous_page(&mut self) { - self.active_page = self.active_page.saturating_sub(1); + self.go_to(self.active_page.saturating_sub(1)); + } + + pub fn go_to(&mut self, active_page: usize) { + self.active_page = active_page; } } diff --git a/core/embed/rust/src/ui/model_tt/component/swipe.rs b/core/embed/rust/src/ui/model_tt/component/swipe.rs index 91d3f79c5..634178c81 100644 --- a/core/embed/rust/src/ui/model_tt/component/swipe.rs +++ b/core/embed/rust/src/ui/model_tt/component/swipe.rs @@ -14,7 +14,7 @@ pub enum SwipeDirection { } pub struct Swipe { - area: Rect, + pub area: Rect, pub allow_up: bool, pub allow_down: bool, pub allow_left: bool,