From 68bda9cee8fce4c136ba6d2b8fa850a60453dcfd Mon Sep 17 00:00:00 2001 From: matejcik Date: Wed, 29 Jan 2025 13:36:57 +0100 Subject: [PATCH] feat(core/rust): introduce trait PaginateFull throughout Delizia PaginateFull uses Pager instead of reporting just the total number of pages. Delizia will rely on this trait; going forward, we'll want PaginateFull to replace Paginate, but this refactor would be too big if we also needed to include Caesar and Bolt in it --- .../rust/src/ui/component/button_request.rs | 4 +- .../rust/src/ui/component/cached_jpeg.rs | 4 + core/embed/rust/src/ui/component/map.rs | 8 +- core/embed/rust/src/ui/component/mod.rs | 2 +- core/embed/rust/src/ui/component/paginated.rs | 36 +++++ core/embed/rust/src/ui/component/placed.rs | 4 + core/embed/rust/src/ui/component/qr_code.rs | 4 + .../rust/src/ui/component/swipe_detect.rs | 28 ++-- .../rust/src/ui/component/text/formatted.rs | 139 ++++++++++------- .../rust/src/ui/component/text/paragraphs.rs | 147 +++++++++++------- core/embed/rust/src/ui/flow/base.rs | 3 +- core/embed/rust/src/ui/flow/page.rs | 61 ++++---- core/embed/rust/src/ui/flow/swipe.rs | 35 +---- .../src/ui/layout_caesar/component/flow.rs | 2 +- .../ui/layout_caesar/component/flow_pages.rs | 8 +- .../component/address_details.rs | 40 +++-- .../src/ui/layout_delizia/component/fido.rs | 3 + .../component/keyboard/passphrase.rs | 11 +- .../component/keyboard/word_count.rs | 4 +- .../src/ui/layout_delizia/component/mod.rs | 2 +- .../layout_delizia/component/number_input.rs | 3 + .../component/number_input_slider.rs | 4 +- .../layout_delizia/component/prompt_screen.rs | 4 +- .../layout_delizia/component/share_words.rs | 41 ++--- .../layout_delizia/component/status_screen.rs | 7 +- .../layout_delizia/component/swipe_content.rs | 59 ++++--- .../component/updatable_more_info.rs | 4 +- .../layout_delizia/component/vertical_menu.rs | 49 +++--- .../ui/layout_delizia/component_msg_obj.rs | 3 +- .../ui/layout_delizia/flow/confirm_action.rs | 14 +- .../ui/layout_delizia/flow/confirm_fido.rs | 15 +- .../flow/continue_recovery_homepage.rs | 8 +- .../layout_delizia/flow/show_share_words.rs | 10 +- .../rust/src/ui/layout_delizia/flow/util.rs | 4 +- 34 files changed, 422 insertions(+), 348 deletions(-) diff --git a/core/embed/rust/src/ui/component/button_request.rs b/core/embed/rust/src/ui/component/button_request.rs index 75e3314227..8686d28302 100644 --- a/core/embed/rust/src/ui/component/button_request.rs +++ b/core/embed/rust/src/ui/component/button_request.rs @@ -73,8 +73,8 @@ impl crate::ui::flow::Swipable for SendButtonReque self.inner.get_swipe_config() } - fn get_internal_page_count(&self) -> usize { - self.inner.get_internal_page_count() + fn get_pager(&self) -> crate::ui::util::Pager { + self.inner.get_pager() } } diff --git a/core/embed/rust/src/ui/component/cached_jpeg.rs b/core/embed/rust/src/ui/component/cached_jpeg.rs index a9d3317a56..020538ad9f 100644 --- a/core/embed/rust/src/ui/component/cached_jpeg.rs +++ b/core/embed/rust/src/ui/component/cached_jpeg.rs @@ -9,6 +9,8 @@ use crate::{ }, }; +use super::paginated::SinglePage; + pub struct CachedJpeg { area: Rect, image_size: Offset, @@ -73,6 +75,8 @@ impl Component for CachedJpeg { } } +impl SinglePage for CachedJpeg {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for CachedJpeg { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/component/map.rs b/core/embed/rust/src/ui/component/map.rs index 1a08196620..ae25e60ff7 100644 --- a/core/embed/rust/src/ui/component/map.rs +++ b/core/embed/rust/src/ui/component/map.rs @@ -43,8 +43,8 @@ where fn get_swipe_config(&self) -> SwipeConfig { self.inner.get_swipe_config() } - fn get_internal_page_count(&self) -> usize { - self.inner.get_internal_page_count() + fn get_pager(&self) -> crate::ui::util::Pager { + self.inner.get_pager() } } @@ -109,7 +109,7 @@ where fn get_swipe_config(&self) -> SwipeConfig { self.inner.get_swipe_config() } - fn get_internal_page_count(&self) -> usize { - self.inner.get_internal_page_count() + fn get_pager(&self) -> crate::ui::util::Pager { + self.inner.get_pager() } } diff --git a/core/embed/rust/src/ui/component/mod.rs b/core/embed/rust/src/ui/component/mod.rs index e15948fdf6..2af6799156 100644 --- a/core/embed/rust/src/ui/component/mod.rs +++ b/core/embed/rust/src/ui/component/mod.rs @@ -48,7 +48,7 @@ pub use map::{MsgMap, PageMap}; pub use marquee::Marquee; pub use maybe::Maybe; pub use pad::Pad; -pub use paginated::{PageMsg, Paginate}; +pub use paginated::{PageMsg, Paginate, PaginateFull}; pub use placed::{FixedHeightBar, Floating, GridPlaced, Split}; pub use qr_code::Qr; #[cfg(feature = "touch")] diff --git a/core/embed/rust/src/ui/component/paginated.rs b/core/embed/rust/src/ui/component/paginated.rs index 75a05ac3ed..e548c35095 100644 --- a/core/embed/rust/src/ui/component/paginated.rs +++ b/core/embed/rust/src/ui/component/paginated.rs @@ -1,3 +1,5 @@ +use crate::ui::util::Pager; + /// Common message type for pagination components. #[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))] pub enum PageMsg { @@ -20,9 +22,43 @@ pub enum PageMsg { SwipeRight, } +/// TRANSITIONAL paginate trait that only optionally returns the current page. +/// Use PaginateFull for the new trait that returns a Pager. pub trait Paginate { /// How many pages of content are there in total? fn page_count(&self) -> usize; /// Navigate to the given page. fn change_page(&mut self, active_page: usize); } + +/// Paginate trait allowing the user to see the internal pager state. +pub trait PaginateFull { + /// What is the internal pager state? + fn pager(&self) -> Pager; + /// Navigate to the given page. + fn change_page(&mut self, active_page: u16); +} + +impl Paginate for T { + fn change_page(&mut self, active_page: usize) { + self.change_page(active_page as u16); + } + + fn page_count(&self) -> usize { + self.pager().total() as usize + } +} + +pub trait SinglePage {} + +impl PaginateFull for T { + fn pager(&self) -> Pager { + Pager::single_page() + } + + fn change_page(&mut self, active_page: u16) { + if active_page != 0 { + unimplemented!() + } + } +} diff --git a/core/embed/rust/src/ui/component/placed.rs b/core/embed/rust/src/ui/component/placed.rs index aae74c23ad..601bf2569e 100644 --- a/core/embed/rust/src/ui/component/placed.rs +++ b/core/embed/rust/src/ui/component/placed.rs @@ -4,6 +4,8 @@ use crate::ui::{ shape::Renderer, }; +use super::paginated::SinglePage; + pub struct GridPlaced { inner: T, grid: Grid, @@ -272,6 +274,8 @@ where } } +impl SinglePage for Split {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Split where diff --git a/core/embed/rust/src/ui/component/qr_code.rs b/core/embed/rust/src/ui/component/qr_code.rs index 252852ad29..d183238b42 100644 --- a/core/embed/rust/src/ui/component/qr_code.rs +++ b/core/embed/rust/src/ui/component/qr_code.rs @@ -12,6 +12,8 @@ use crate::{ }, }; +use super::paginated::SinglePage; + const NVERSIONS: usize = 10; // range of versions (=capacities) that we support const THRESHOLDS_BINARY: [usize; NVERSIONS] = [14, 26, 42, 62, 84, 106, 122, 152, 180, 213]; const THRESHOLDS_ALPHANUM: [usize; NVERSIONS] = [20, 38, 61, 90, 122, 154, 178, 221, 262, 311]; @@ -123,6 +125,8 @@ impl Component for Qr { } } +impl SinglePage for Qr {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Qr { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/component/swipe_detect.rs b/core/embed/rust/src/ui/component/swipe_detect.rs index 5ea202e789..f7eef0886f 100644 --- a/core/embed/rust/src/ui/component/swipe_detect.rs +++ b/core/embed/rust/src/ui/component/swipe_detect.rs @@ -6,7 +6,7 @@ use crate::{ constant::screen, event::{SwipeEvent, TouchEvent}, geometry::{Axis, Direction, Offset, Point}, - util::animation_disabled, + util::{animation_disabled, Pager}, }, }; @@ -106,23 +106,21 @@ impl SwipeConfig { self } - pub fn with_pagination(mut self, current_page: u16, total_pages: u16) -> Self { - let has_prev = current_page > 0; - let has_next = current_page < total_pages.saturating_sub(1); + pub fn with_pager(mut self, pager: Pager) -> Self { match self.page_axis { Some(Axis::Horizontal) => { - if has_prev { + if pager.has_prev() { self.right = Some(SwipeSettings::default()); } - if has_next { + if pager.has_next() { self.left = Some(SwipeSettings::default()); } } Some(Axis::Vertical) => { - if has_prev { + if pager.has_prev() { self.down = Some(SwipeSettings::default()); } - if has_next { + if pager.has_next() { self.up = Some(SwipeSettings::default()); } } @@ -131,15 +129,13 @@ impl SwipeConfig { self } - pub fn paging_event(&self, dir: Direction, current_page: u16, total_pages: u16) -> u16 { - let prev_page = current_page.saturating_sub(1); - let next_page = (current_page + 1).min(total_pages.saturating_sub(1)); + pub fn paging_event(&self, dir: Direction, pager: Pager) -> u16 { match (self.page_axis, dir) { - (Some(Axis::Horizontal), Direction::Right) => prev_page, - (Some(Axis::Horizontal), Direction::Left) => next_page, - (Some(Axis::Vertical), Direction::Down) => prev_page, - (Some(Axis::Vertical), Direction::Up) => next_page, - _ => current_page, + (Some(Axis::Horizontal), Direction::Right) => pager.prev(), + (Some(Axis::Horizontal), Direction::Left) => pager.next(), + (Some(Axis::Vertical), Direction::Down) => pager.prev(), + (Some(Axis::Vertical), Direction::Up) => pager.next(), + _ => pager.current(), } } } diff --git a/core/embed/rust/src/ui/component/text/formatted.rs b/core/embed/rust/src/ui/component/text/formatted.rs index f41ebc956e..881cb9538c 100644 --- a/core/embed/rust/src/ui/component/text/formatted.rs +++ b/core/embed/rust/src/ui/component/text/formatted.rs @@ -1,7 +1,8 @@ use crate::ui::{ - component::{Component, Event, EventCtx, Never, Paginate}, + component::{Component, Event, EventCtx, Never, PaginateFull}, geometry::{Alignment, Offset, Rect}, shape::Renderer, + util::Pager, }; use super::{ @@ -15,6 +16,7 @@ pub struct FormattedText { vertical: Alignment, char_offset: usize, y_offset: i16, + pager: Pager, } impl FormattedText { @@ -24,6 +26,7 @@ impl FormattedText { vertical: Alignment::Start, char_offset: 0, y_offset: 0, + pager: Pager::single_page(), } } @@ -32,18 +35,9 @@ impl FormattedText { self } - pub(crate) fn layout_content(&self, sink: &mut dyn LayoutSink) -> LayoutFit { - self.layout_content_with_offset(sink, self.char_offset, self.y_offset) - } - - fn layout_content_with_offset( - &self, - sink: &mut dyn LayoutSink, - char_offset: usize, - y_offset: i16, - ) -> LayoutFit { + fn layout_content(&self, sink: &mut dyn LayoutSink) -> LayoutFit { self.op_layout - .layout_ops(char_offset, Offset::y(y_offset), sink) + .layout_ops(self.char_offset, Offset::y(self.y_offset), sink) } fn align_vertically(&mut self, content_height: i16) { @@ -58,19 +52,79 @@ impl FormattedText { Alignment::End => bounds_height - content_height, } } + + #[cfg(feature = "ui_debug")] + pub(crate) fn trace_lines_as_list(&self, l: &mut dyn crate::trace::ListTracer) -> LayoutFit { + use crate::ui::component::text::layout::trace::TraceSink; + let result = self.layout_content(&mut TraceSink(l)); + result + } } // Pagination -impl Paginate for FormattedText { - fn page_count(&self) -> usize { +impl PaginateFull for FormattedText { + fn pager(&self) -> Pager { + self.pager + } + + fn change_page(&mut self, to_page: u16) { + let to_page = to_page.min(self.pager.total() - 1); + + // reset current position if needed and calculate how many pages forward we need + // to go + self.y_offset = 0; + let mut pages_remaining = if to_page < self.pager.current() { + self.char_offset = 0; + to_page + } else { + to_page - self.pager.current() + }; + + // move forward until we arrive at the wanted page + let mut fit = self.layout_content(&mut TextNoOp); + while pages_remaining > 0 { + match fit { + LayoutFit::Fitting { .. } => { + break; + } + LayoutFit::OutOfBounds { + processed_chars, .. + } => { + pages_remaining -= 1; + self.char_offset += processed_chars; + fit = self.layout_content(&mut TextNoOp); + } + } + } + // Setting appropriate self.y_offset + self.align_vertically(fit.height()); + // Update the pager + self.pager.set_current(to_page); + } +} + +impl Component for FormattedText { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + if self.op_layout.layout.bounds == bounds { + // Skip placement logic (and resetting pager) if the bounds haven't changed. + return bounds; + } + + self.op_layout.place(bounds); + + // reset current position + self.char_offset = 0; + self.y_offset = 0; + let mut page_count = 1; // There's always at least one page. + let mut first_fit = None; - let mut char_offset = 0; - - // Looping through the content and counting pages - // until we finally fit. + // Looping through the content and counting pages until we finally fit. loop { - let fit = self.layout_content_with_offset(&mut TextNoOp, char_offset, 0); + let fit = self.layout_content(&mut TextNoOp); + first_fit.get_or_insert(fit); match fit { LayoutFit::Fitting { .. } => { break; @@ -79,50 +133,18 @@ impl Paginate for FormattedText { processed_chars, .. } => { page_count += 1; - char_offset += processed_chars; - } - } - } - - page_count - } - - fn change_page(&mut self, to_page: usize) { - let mut active_page = 0; - - // Make sure we're starting from the beginning. - self.char_offset = 0; - self.y_offset = 0; - - // Looping through the content until we arrive at - // the wanted page. - let mut fit = self.layout_content(&mut TextNoOp); - while active_page < to_page { - match fit { - LayoutFit::Fitting { .. } => { - break; - } - LayoutFit::OutOfBounds { - processed_chars, .. - } => { - active_page += 1; self.char_offset += processed_chars; - fit = self.layout_content(&mut TextNoOp); } } } - // Setting appropriate self.y_offset - self.align_vertically(fit.height()); - } -} -impl Component for FormattedText { - type Msg = Never; + // reset position to start + self.char_offset = 0; + // align vertically and set pager + let first_fit = unwrap!(first_fit); + self.align_vertically(first_fit.height()); + self.pager = Pager::new(page_count); - fn place(&mut self, bounds: Rect) -> Rect { - self.op_layout.place(bounds); - let height = self.layout_content(&mut TextNoOp).height(); - self.align_vertically(height); bounds } @@ -140,12 +162,11 @@ impl Component for FormattedText { #[cfg(feature = "ui_debug")] impl crate::trace::Trace for FormattedText { fn trace(&self, t: &mut dyn crate::trace::Tracer) { - use crate::ui::component::text::layout::trace::TraceSink; use core::cell::Cell; let fit: Cell> = Cell::new(None); t.component("FormattedText"); t.in_list("text", &|l| { - let result = self.layout_content(&mut TraceSink(l)); + let result = self.trace_lines_as_list(l); fit.set(Some(result)); }); t.bool("fits", matches!(fit.get(), Some(LayoutFit::Fitting { .. }))); diff --git a/core/embed/rust/src/ui/component/text/paragraphs.rs b/core/embed/rust/src/ui/component/text/paragraphs.rs index e1075a7554..bedbb8506d 100644 --- a/core/embed/rust/src/ui/component/text/paragraphs.rs +++ b/core/embed/rust/src/ui/component/text/paragraphs.rs @@ -3,10 +3,11 @@ use heapless::Vec; use crate::{ strutil::TString, ui::{ - component::{Component, Event, EventCtx, Never, Paginate}, + component::{paginated::SinglePage, Component, Event, EventCtx, Never, PaginateFull}, display::{font::Font, toif::Icon, Color}, geometry::{Alignment, Dimensions, Insets, LinearPlacement, Offset, Point, Rect}, shape::{self, Renderer}, + util::Pager, }, }; @@ -51,6 +52,7 @@ pub struct Paragraphs { offset: PageOffset, visible: Vec, source: T, + pager: Pager, } impl<'a, T> Paragraphs @@ -66,6 +68,7 @@ where offset: PageOffset::default(), visible: Vec::new(), source, + pager: Pager::single_page(), } } @@ -100,12 +103,6 @@ where result.unwrap_or(self.area) } - pub fn current_page(&self) -> usize { - self.break_pages() - .position(|offset| offset == self.offset) - .unwrap_or(0) - } - /// Update bounding boxes of paragraphs on the current page. First determine /// the number of visible paragraphs and their sizes. These are then /// arranged according to the layout. @@ -127,28 +124,42 @@ where let full_height = area.height(); while offset.par < source.size() { - let (next_offset, remaining_area, layout) = offset.advance(area, source, full_height); - if let Some(layout) = layout { + let advance = offset.advance(area, source, full_height); + if let Some(layout) = advance.layout { unwrap!(visible.push(layout)); } - if let Some(remaining_area) = remaining_area { + if let Some(remaining_area) = advance.remaining_area { #[cfg(feature = "ui_debug")] - assert_eq!(next_offset.par, offset.par + 1); + assert_eq!(advance.offset.par, offset.par + 1); area = remaining_area; - offset = next_offset; + offset = advance.offset; } else { break; } } } - fn break_pages(&self) -> PageBreakIterator { + fn break_pages_from(&self, offset: Option) -> PageBreakIterator { PageBreakIterator { paragraphs: self, - current: None, + current: offset, } } + /// Break pages from the start of the document. + /// + /// The first pagebreak is at the start of the first screen. + fn break_pages_from_start(&self) -> PageBreakIterator { + self.break_pages_from(None) + } + + /// Break pages, continuing from the current page. + /// + /// The first pagebreak is at the start of the next screen. + fn break_pages_from_next(&self) -> PageBreakIterator { + self.break_pages_from(Some(self.offset)) + } + /// Iterate over visible layouts (bounding box, style) together /// with corresponding string content. Should not get monomorphized. fn foreach_visible<'b>( @@ -184,7 +195,13 @@ where type Msg = Never; fn place(&mut self, bounds: Rect) -> Rect { + let recalc = self.area != bounds; self.area = bounds; + if recalc { + self.offset = PageOffset::default(); + let total_pages = self.break_pages_from_start().count().max(1); + self.pager = Pager::new(total_pages as u16); + } self.change_offset(self.offset); self.area } @@ -205,22 +222,32 @@ where } } -impl<'a, T> Paginate for Paragraphs +impl<'a, T> PaginateFull for Paragraphs where T: ParagraphSource<'a>, { - fn page_count(&self) -> usize { - // There's always at least one page. - self.break_pages().count().max(1) + fn pager(&self) -> Pager { + self.pager } - fn change_page(&mut self, to_page: usize) { - if let Some(offset) = self.break_pages().nth(to_page) { - self.change_offset(offset) + fn change_page(&mut self, to_page: u16) { + use core::cmp::Ordering; + + let offset = match to_page.cmp(&self.pager.current()) { + Ordering::Equal => return, + Ordering::Greater => self + .break_pages_from_next() + .nth((to_page - self.pager.current() - 1) as usize), + Ordering::Less => self.break_pages_from_start().nth(to_page as usize), + }; + if let Some(offset) = offset { + self.change_offset(offset); + self.pager.set_current(to_page); } else { // Should not happen, set index to first paragraph and render empty page. self.offset = PageOffset::default(); - self.visible.clear() + self.visible.clear(); + self.pager.set_current(0); } } } @@ -363,7 +390,7 @@ impl Dimensions for TextLayoutProxy { } } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] struct PageOffset { /// Index of paragraph. par: usize, @@ -372,6 +399,13 @@ struct PageOffset { chr: usize, } +/// Helper struct for `PageOffset::advance` +struct PageOffsetAdvance { + offset: PageOffset, + remaining_area: Option, + layout: Option, +} + impl PageOffset { /// Given a `PageOffset` and a `Rect` area, returns: /// @@ -388,29 +422,34 @@ impl PageOffset { area: Rect, source: &dyn ParagraphSource<'_>, full_height: i16, - ) -> (PageOffset, Option, Option) { + ) -> PageOffsetAdvance { let paragraph = source.at(self.par, self.chr); // Skip empty paragraphs. if paragraph.content().is_empty() { self.par += 1; self.chr = 0; - return (self, Some(area), None); + return PageOffsetAdvance { + offset: self, + remaining_area: Some(area), + layout: None, + }; } - // Handle the `no_break` flag used to keep key-value pair on the same page. - if paragraph.no_break && self.chr == 0 { - if let Some(next_paragraph) = - (self.par + 1 < source.size()).then(|| source.at(self.par + 1, 0)) + // Handle the `no_break` flag used to keep key-value pair on the same page: + // * no_break is set + // * we're at the start of a paragraph ("key") + // * there is a next paragraph ("value") + // then check if the pair fits on the next page. + if paragraph.no_break && self.chr == 0 && self.par + 1 < source.size() { + let next_paragraph = source.at(self.par + 1, 0); + if Self::should_place_pair_on_next_page(¶graph, &next_paragraph, area, full_height) { - if Self::should_place_pair_on_next_page( - ¶graph, - &next_paragraph, - area, - full_height, - ) { - return (self, None, None); - } + return PageOffsetAdvance { + offset: self, + remaining_area: None, + layout: None, + }; } } @@ -441,11 +480,11 @@ impl PageOffset { } } - ( - self, - Some(remaining_area).filter(|_| !page_full), - Some(layout).filter(|_| fit.height() > 0), - ) + PageOffsetAdvance { + offset: self, + remaining_area: (!page_full).then_some(remaining_area), + layout: (fit.height() > 0).then_some(layout), + } } fn should_place_pair_on_next_page( @@ -505,18 +544,17 @@ impl<'a, T: ParagraphSource<'a>> PageBreakIterator<'_, T> { let full_height = area.height(); while offset.par < paragraphs.size() { - let (next_offset, remaining_area, _layout) = - offset.advance(area, paragraphs, full_height); - if next_offset.par >= paragraphs.size() { + let advance = offset.advance(area, paragraphs, full_height); + if advance.offset.par >= paragraphs.size() { // Last page. return None; - } else if let Some(remaining_area) = remaining_area { + } else if let Some(remaining_area) = advance.remaining_area { #[cfg(feature = "ui_debug")] - assert_eq!(next_offset.par, offset.par + 1); + assert_eq!(advance.offset.par, offset.par + 1); area = remaining_area; - offset = next_offset; + offset = advance.offset; } else { - return Some(next_offset); + return Some(advance.offset); } } @@ -693,16 +731,7 @@ where } } -impl<'a, T> Paginate for Checklist -where - T: ParagraphSource<'a>, -{ - fn page_count(&self) -> usize { - 1 - } - - fn change_page(&mut self, _to_page: usize) {} -} +impl SinglePage for Checklist {} #[cfg(feature = "ui_debug")] impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Checklist { diff --git a/core/embed/rust/src/ui/flow/base.rs b/core/embed/rust/src/ui/flow/base.rs index d16bd3ec86..f17f96c230 100644 --- a/core/embed/rust/src/ui/flow/base.rs +++ b/core/embed/rust/src/ui/flow/base.rs @@ -1,6 +1,7 @@ use crate::ui::{ component::{base::AttachType, swipe_detect::SwipeConfig}, geometry::Direction, + util::Pager, }; pub use crate::ui::component::FlowMsg; @@ -8,7 +9,7 @@ pub use crate::ui::component::FlowMsg; pub trait Swipable { fn get_swipe_config(&self) -> SwipeConfig; - fn get_internal_page_count(&self) -> usize; + fn get_pager(&self) -> Pager; } /// Composable event handler result. diff --git a/core/embed/rust/src/ui/flow/page.rs b/core/embed/rust/src/ui/flow/page.rs index 48c410ed68..9776be49e7 100644 --- a/core/embed/rust/src/ui/flow/page.rs +++ b/core/embed/rust/src/ui/flow/page.rs @@ -1,8 +1,9 @@ use crate::ui::{ - component::{Component, Event, EventCtx, Paginate}, + component::{Component, Event, EventCtx, PaginateFull}, event::SwipeEvent, geometry::{Axis, Direction, Rect}, shape::Renderer, + util::Pager, }; /// Allows any implementor of `Paginate` to be part of `Swipable` UI flow. @@ -11,19 +12,17 @@ pub struct SwipePage { inner: T, bounds: Rect, axis: Axis, - pages: usize, - current: usize, - limit: Option, + pager: Pager, + limit: Option, } -impl SwipePage { +impl SwipePage { pub fn vertical(inner: T) -> Self { Self { inner, bounds: Rect::zero(), axis: Axis::Vertical, - pages: 1, - current: 0, + pager: Pager::single_page(), limit: None, } } @@ -33,8 +32,7 @@ impl SwipePage { inner, bounds: Rect::zero(), axis: Axis::Horizontal, - pages: 1, - current: 0, + pager: Pager::single_page(), limit: None, } } @@ -43,55 +41,47 @@ impl SwipePage { &self.inner } - pub fn with_limit(mut self, limit: Option) -> Self { + pub fn with_limit(mut self, limit: Option) -> Self { self.limit = limit; self } - - pub fn page_count(&self) -> usize { - self.pages - } - - pub fn current_page(&self) -> usize { - self.current - } } -impl Component for SwipePage { +impl Component for SwipePage { type Msg = T::Msg; fn place(&mut self, bounds: Rect) -> Rect { self.bounds = self.inner.place(bounds); - self.pages = self.inner.page_count(); + self.pager = self.inner.pager(); if let Some(limit) = self.limit { - self.pages = self.pages.min(limit); + self.pager = self.pager.with_limit(limit); } self.bounds } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - ctx.set_page_count(self.pages); + ctx.set_page_count(self.pager.total() as usize); if let Event::Swipe(SwipeEvent::End(direction)) = event { match (self.axis, direction) { (Axis::Vertical, Direction::Up) => { - self.current = (self.current + 1).min(self.pages - 1); - self.inner.change_page(self.current); + self.pager.goto_next(); + self.inner.change_page(self.pager.current()); ctx.request_paint(); } (Axis::Vertical, Direction::Down) => { - self.current = self.current.saturating_sub(1); - self.inner.change_page(self.current); + self.pager.goto_prev(); + self.inner.change_page(self.pager.current()); ctx.request_paint(); } (Axis::Horizontal, Direction::Left) => { - self.current = (self.current + 1).min(self.pages - 1); - self.inner.change_page(self.current); + self.pager.goto_next(); + self.inner.change_page(self.pager.current()); ctx.request_paint(); } (Axis::Horizontal, Direction::Right) => { - self.current = self.current.saturating_sub(1); - self.inner.change_page(self.current); + self.pager.goto_prev(); + self.inner.change_page(self.pager.current()); ctx.request_paint(); } _ => {} @@ -106,6 +96,17 @@ impl Component for SwipePage { } } +impl PaginateFull for SwipePage { + fn pager(&self) -> Pager { + self.pager + } + + fn change_page(&mut self, to_page: u16) { + self.pager.set_current(to_page); + self.inner.change_page(self.pager.current()); + } +} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for SwipePage where diff --git a/core/embed/rust/src/ui/flow/swipe.rs b/core/embed/rust/src/ui/flow/swipe.rs index a4039072b4..4433d8d355 100644 --- a/core/embed/rust/src/ui/flow/swipe.rs +++ b/core/embed/rust/src/ui/flow/swipe.rs @@ -103,10 +103,6 @@ pub struct SwipeFlow { swipe: SwipeDetect, /// Swipe allowed allow_swipe: bool, - /// Current page index - internal_page_idx: u16, - /// Internal pages count - internal_pages: u16, /// If triggering swipe by event, make this decision instead of default /// after the swipe. pending_decision: Option, @@ -123,8 +119,6 @@ impl SwipeFlow { swipe: SwipeDetect::new(), store: Vec::new(), allow_swipe: true, - internal_page_idx: 0, - internal_pages: 1, pending_decision: None, lifecycle_state: LayoutState::Initial, returned_value: None, @@ -154,19 +148,6 @@ impl SwipeFlow { &mut self.store[self.state.index()] } - fn update_page_count(&mut self, attach_type: AttachType) { - // update page count - self.internal_pages = self.current_page_mut().get_internal_page_count() as u16; - // reset internal state: - self.internal_page_idx = if let Swipe(Direction::Down) = attach_type { - // if coming from below, set to the last page - self.internal_pages.saturating_sub(1) - } else { - // else reset to the first page - 0 - }; - } - /// Transition to a different state. /// /// This is the only way to change the current flow state. @@ -183,7 +164,6 @@ impl SwipeFlow { self.current_page_mut() .event(ctx, Event::Attach(attach_type)); - self.update_page_count(attach_type); ctx.request_paint(); } @@ -209,27 +189,20 @@ impl SwipeFlow { let mut decision = Decision::Nothing; let mut return_transition: AttachType = AttachType::Initial; - if let Event::Attach(attach_type) = event { - self.update_page_count(attach_type); - } - let mut attach = false; let event = if self.allow_swipe { let page = self.current_page(); - let config = page - .get_swipe_config() - .with_pagination(self.internal_page_idx, self.internal_pages); + let pager = page.get_pager(); + let config = page.get_swipe_config().with_pager(pager); match self.swipe.event(ctx, event, config) { Some(SwipeEvent::End(dir)) => { return_transition = AttachType::Swipe(dir); - let new_internal_page_idx = - config.paging_event(dir, self.internal_page_idx, self.internal_pages); - if new_internal_page_idx != self.internal_page_idx { + let new_internal_page_idx = config.paging_event(dir, pager); + if new_internal_page_idx != pager.current() { // internal paging event - self.internal_page_idx = new_internal_page_idx; decision = Decision::Nothing; attach = true; } else if let Some(override_decision) = self.pending_decision.take() { diff --git a/core/embed/rust/src/ui/layout_caesar/component/flow.rs b/core/embed/rust/src/ui/layout_caesar/component/flow.rs index 0d0f8892cc..033b965fc1 100644 --- a/core/embed/rust/src/ui/layout_caesar/component/flow.rs +++ b/core/embed/rust/src/ui/layout_caesar/component/flow.rs @@ -121,8 +121,8 @@ where fn update(&mut self, ctx: &mut EventCtx, get_new_page: bool) { if get_new_page { self.change_current_page(ctx); + self.current_page.place(self.content_area); } - self.current_page.place(self.content_area); self.set_buttons(ctx); self.scrollbar.request_complete_repaint(ctx); self.clear_and_repaint(ctx); diff --git a/core/embed/rust/src/ui/layout_caesar/component/flow_pages.rs b/core/embed/rust/src/ui/layout_caesar/component/flow_pages.rs index ca1f31aa1d..a0177fbb94 100644 --- a/core/embed/rust/src/ui/layout_caesar/component/flow_pages.rs +++ b/core/embed/rust/src/ui/layout_caesar/component/flow_pages.rs @@ -209,14 +209,10 @@ impl Paginate for Page { } // DEBUG-ONLY SECTION BELOW - -#[cfg(feature = "ui_debug")] -use crate::ui::component::text::layout::LayoutFit; - #[cfg(feature = "ui_debug")] impl crate::trace::Trace for Page { fn trace(&self, t: &mut dyn crate::trace::Tracer) { - use crate::ui::component::text::layout::trace::TraceSink; + use crate::ui::component::text::layout::LayoutFit; use core::cell::Cell; let fit: Cell> = Cell::new(None); t.component("Page"); @@ -227,7 +223,7 @@ impl crate::trace::Trace for Page { t.int("active_page", self.current_page as i64); t.int("page_count", self.page_count as i64); t.in_list("text", &|l| { - let result = self.formatted.layout_content(&mut TraceSink(l)); + let result = self.formatted.trace_lines_as_list(l); fit.set(Some(result)); }); } diff --git a/core/embed/rust/src/ui/layout_delizia/component/address_details.rs b/core/embed/rust/src/ui/layout_delizia/component/address_details.rs index 16ea2025af..0e9b98745c 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/address_details.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/address_details.rs @@ -8,12 +8,13 @@ use crate::{ component::{ swipe_detect::{SwipeConfig, SwipeSettings}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, - Component, Event, EventCtx, Paginate, + Component, Event, EventCtx, PaginateFull, }, event::SwipeEvent, flow::Swipable, geometry::{Direction, Rect}, shape::Renderer, + util::Pager, }, }; @@ -26,7 +27,7 @@ pub struct AddressDetails { xpub_view: Frame>>, xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>, xpub_page_count: Vec, - current_page: usize, + current_page: u16, } impl AddressDetails { @@ -84,7 +85,7 @@ impl AddressDetails { .map_err(|_| Error::OutOfRange) } - fn switch_xpub(&mut self, i: usize, page: usize) -> usize { + fn switch_xpub(&mut self, i: usize, page: u16) -> usize { // Context is needed for updating child so that it can request repaint. In this // case the parent component that handles paging always requests complete // repaint after page change so we can use a dummy context here. @@ -92,19 +93,17 @@ impl AddressDetails { self.xpub_view.update_title(&mut dummy_ctx, self.xpubs[i].0); self.xpub_view.update_content(&mut dummy_ctx, |_ctx, p| { p.inner_mut().update(self.xpubs[i].1); - let npages = p.page_count(); + let npages = p.pager().total() as usize; p.change_page(page); npages }) } - fn lookup(&self, scrollbar_page: usize) -> (usize, usize) { + fn lookup(&self, scrollbar_page: u16) -> (usize, u16) { let mut xpub_index = 0; let mut xpub_page = scrollbar_page; - for page_count in self.xpub_page_count.iter().map(|pc| { - let upc: usize = (*pc).into(); - upc - }) { + for page_count in self.xpub_page_count.iter() { + let page_count = *page_count as u16; if page_count <= xpub_page { xpub_page -= page_count; xpub_index += 1; @@ -116,12 +115,13 @@ impl AddressDetails { } } -impl Paginate for AddressDetails { - fn page_count(&self) -> usize { - self.get_internal_page_count() +impl PaginateFull for AddressDetails { + fn pager(&self) -> Pager { + let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum(); + Pager::new(total_xpub_pages as u16 + 1).with_current(self.current_page) } - fn change_page(&mut self, to_page: usize) { + fn change_page(&mut self, to_page: u16) { self.current_page = to_page; if to_page > 0 { let i = to_page - 1; @@ -148,17 +148,14 @@ impl Component for AddressDetails { } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { - ctx.set_page_count(self.page_count()); + ctx.set_page_count(self.pager().total() as usize); match event { Event::Swipe(SwipeEvent::End(Direction::Right)) => { - let to_page = self.current_page.saturating_sub(1); + let to_page = self.pager().prev(); self.change_page(to_page); } Event::Swipe(SwipeEvent::End(Direction::Left)) => { - let to_page = self - .current_page - .saturating_add(1) - .min(self.page_count() - 1); + let to_page = self.pager().next(); self.change_page(to_page); } _ => {} @@ -190,9 +187,8 @@ impl Swipable for AddressDetails { } } - fn get_internal_page_count(&self) -> usize { - let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum(); - 1usize.saturating_add(total_xpub_pages.into()) + fn get_pager(&self) -> Pager { + self.pager() } } diff --git a/core/embed/rust/src/ui/layout_delizia/component/fido.rs b/core/embed/rust/src/ui/layout_delizia/component/fido.rs index 4f1452e60c..3da375857d 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/fido.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/fido.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{ image::Image, + paginated::SinglePage, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs}, Component, Event, EventCtx, }, @@ -77,6 +78,8 @@ impl TString<'static>> Component for FidoCredential { } } +impl TString<'static>> SinglePage for FidoCredential {} + #[cfg(feature = "ui_debug")] impl TString<'static>> crate::trace::Trace for FidoCredential { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/layout_delizia/component/keyboard/passphrase.rs b/core/embed/rust/src/ui/layout_delizia/component/keyboard/passphrase.rs index 7636dde139..68677b35fb 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/keyboard/passphrase.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/keyboard/passphrase.rs @@ -8,9 +8,8 @@ use crate::{ }, display, geometry::{Alignment, Direction, Grid, Insets, Offset, Rect}, - shape, - shape::Renderer, - util::long_line_content_with_ellipsis, + shape::{self, Renderer}, + util::{long_line_content_with_ellipsis, Pager}, }, }; @@ -88,7 +87,6 @@ pub struct PassphraseKeyboard { active_layout: KeyboardLayout, fade: Cell, swipe_config: SwipeConfig, // FIXME: how about page_swipe - internal_page_cnt: usize, } const PAGE_COUNT: usize = 4; @@ -159,7 +157,6 @@ impl PassphraseKeyboard { active_layout, fade: Cell::new(false), swipe_config: SwipeConfig::new(), - internal_page_cnt: 1, } } @@ -481,8 +478,8 @@ impl crate::ui::flow::Swipable for PassphraseKeyboard { self.swipe_config } - fn get_internal_page_count(&self) -> usize { - self.internal_page_cnt + fn get_pager(&self) -> Pager { + Pager::single_page() } } diff --git a/core/embed/rust/src/ui/layout_delizia/component/keyboard/word_count.rs b/core/embed/rust/src/ui/layout_delizia/component/keyboard/word_count.rs index df13ee454d..c18afc3695 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/keyboard/word_count.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/keyboard/word_count.rs @@ -1,5 +1,5 @@ use crate::ui::{ - component::{Component, Event, EventCtx}, + component::{paginated::SinglePage, Component, Event, EventCtx}, geometry::{Alignment, Grid, GridCellSpan, Rect}, shape::Renderer, }; @@ -72,6 +72,8 @@ impl Component for SelectWordCount { } } +impl SinglePage for SelectWordCount {} + pub struct ValueKeypad { button: [Button; Self::NUMBERS.len()], keypad_area: Rect, diff --git a/core/embed/rust/src/ui/layout_delizia/component/mod.rs b/core/embed/rust/src/ui/layout_delizia/component/mod.rs index 113ab70d77..586e901b77 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/mod.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/mod.rs @@ -79,7 +79,7 @@ pub use scroll::ScrollBar; #[cfg(feature = "translations")] pub use share_words::ShareWords; pub use status_screen::StatusScreen; -pub use swipe_content::{InternallySwipable, InternallySwipableContent, SwipeContent}; +pub use swipe_content::{InternallySwipableContent, SwipeContent}; #[cfg(feature = "translations")] pub use swipe_up_screen::{SwipeUpScreen, SwipeUpScreenMsg}; #[cfg(feature = "translations")] diff --git a/core/embed/rust/src/ui/layout_delizia/component/number_input.rs b/core/embed/rust/src/ui/layout_delizia/component/number_input.rs index a5a50d2c59..c6140679de 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/number_input.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/number_input.rs @@ -3,6 +3,7 @@ use crate::{ strutil::{self, TString}, ui::{ component::{ + paginated::SinglePage, text::paragraphs::{Paragraph, Paragraphs}, Component, Event, EventCtx, Pad, }, @@ -79,6 +80,8 @@ impl Component for NumberInputDialog { } } +impl SinglePage for NumberInputDialog {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for NumberInputDialog { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/layout_delizia/component/number_input_slider.rs b/core/embed/rust/src/ui/layout_delizia/component/number_input_slider.rs index e70f533f43..0dae882ab6 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/number_input_slider.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/number_input_slider.rs @@ -3,7 +3,7 @@ use crate::{ strutil::{ShortString, TString}, translations::TR, ui::{ - component::{Component, Event, EventCtx}, + component::{paginated::SinglePage, Component, Event, EventCtx}, constant::screen, event::TouchEvent, geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, @@ -101,6 +101,8 @@ impl Component for NumberInputSliderDialog { } } +impl SinglePage for NumberInputSliderDialog {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for NumberInputSliderDialog { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/layout_delizia/component/prompt_screen.rs b/core/embed/rust/src/ui/layout_delizia/component/prompt_screen.rs index 4e4f37ccc0..f1c39cb5bb 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/prompt_screen.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/prompt_screen.rs @@ -1,5 +1,5 @@ use crate::ui::{ - component::{Component, Event, EventCtx}, + component::{paginated::SinglePage, Component, Event, EventCtx}, geometry::Rect, shape::Renderer, }; @@ -115,6 +115,8 @@ impl Component for PromptScreen { } } +impl SinglePage for PromptScreen {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for PromptScreen { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/layout_delizia/component/share_words.rs b/core/embed/rust/src/ui/layout_delizia/component/share_words.rs index 01182b0df4..b76104a1a4 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/share_words.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/share_words.rs @@ -2,19 +2,20 @@ use crate::{ strutil::TString, translations::TR, ui::{ - component::{base::AttachType, text::TextStyle, Component, Event, EventCtx, Never}, + component::{ + base::AttachType, paginated::PaginateFull, text::TextStyle, Component, Event, EventCtx, + Never, + }, event::SwipeEvent, geometry::{Alignment, Alignment2D, Direction, Insets, Offset, Rect}, shape::{self, Renderer}, + util::Pager, }, }; use heapless::Vec; -use super::{ - super::component::{swipe_content::SwipeAttachAnimation, InternallySwipable}, - theme, -}; +use super::{super::component::swipe_content::SwipeAttachAnimation, theme}; const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less @@ -26,8 +27,8 @@ type IndexVec = Vec; pub struct ShareWords<'a> { share_words: Vec, MAX_WORDS>, subtitle: TString<'static>, - page_index: i16, - next_index: i16, + page_index: u16, + next_index: u16, /// Area reserved for a shown word from mnemonic/share area_word: Rect, progress: i16, @@ -55,11 +56,11 @@ impl<'a> ShareWords<'a> { } fn is_first_page(&self) -> bool { - self.page_index == 0 + self.pager().is_first() } fn is_final_page(&self) -> bool { - self.page_index == self.share_words.len() as i16 - 1 + self.pager().is_last() } fn find_repeated(share_words: &[TString]) -> IndexVec { @@ -85,9 +86,9 @@ impl<'a> ShareWords<'a> { (self.subtitle, &theme::TEXT_SUB_GREY) } - fn render_word<'s>(&self, word_index: i16, target: &mut impl Renderer<'s>, area: Rect) { + fn render_word<'s>(&self, word_index: u16, target: &mut impl Renderer<'s>, area: Rect) { // the share word - if word_index >= self.share_words.len() as _ || word_index < 0 { + if word_index >= self.share_words.len() as _ { return; } let word = self.share_words[word_index as usize]; @@ -157,13 +158,13 @@ impl<'a> Component for ShareWords<'a> { Event::Swipe(SwipeEvent::End(dir)) => match dir { Direction::Up if !self.is_final_page() => { self.progress = 0; - self.page_index = (self.page_index + 1).min(self.share_words.len() as i16 - 1); + self.page_index = self.pager().next(); self.wait_for_attach = true; ctx.request_paint(); } Direction::Down if !self.is_first_page() => { self.progress = 0; - self.page_index = self.page_index.saturating_sub(1); + self.page_index = self.pager().prev(); self.wait_for_attach = true; ctx.request_paint(); } @@ -172,11 +173,11 @@ impl<'a> Component for ShareWords<'a> { Event::Swipe(SwipeEvent::Move(dir, progress)) => { match dir { Direction::Up => { - self.next_index = self.page_index + 1; + self.next_index = self.pager().next(); self.progress = progress; } Direction::Down => { - self.next_index = self.page_index - 1; + self.next_index = self.pager().prev(); self.progress = progress; } _ => {} @@ -241,13 +242,13 @@ impl<'a> Component for ShareWords<'a> { } } -impl InternallySwipable for ShareWords<'_> { - fn current_page(&self) -> usize { - self.page_index as usize +impl PaginateFull for ShareWords<'_> { + fn pager(&self) -> Pager { + Pager::new(self.share_words.len() as u16).with_current(self.page_index) } - fn num_pages(&self) -> usize { - self.share_words.len() + fn change_page(&mut self, active_page: u16) { + unimplemented!() } } diff --git a/core/embed/rust/src/ui/layout_delizia/component/status_screen.rs b/core/embed/rust/src/ui/layout_delizia/component/status_screen.rs index a20688e856..9016a74a0a 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/status_screen.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/status_screen.rs @@ -3,13 +3,12 @@ use crate::{ strutil::TString, time::{Duration, Stopwatch}, ui::{ - component::{Component, Event, EventCtx, Label, Timeout}, + component::{paginated::SinglePage, Component, Event, EventCtx, Label, Timeout}, constant::screen, display::{Color, Icon}, geometry::{Alignment, Alignment2D, Insets, Point, Rect}, lerp::Lerp, - shape, - shape::Renderer, + shape::{self, Renderer}, util::animation_disabled, }, }; @@ -276,6 +275,8 @@ impl Component for StatusScreen { } } +impl SinglePage for StatusScreen {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for StatusScreen { fn trace(&self, t: &mut dyn crate::trace::Tracer) { diff --git a/core/embed/rust/src/ui/layout_delizia/component/swipe_content.rs b/core/embed/rust/src/ui/layout_delizia/component/swipe_content.rs index 8d2e18f558..12dd34232e 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/swipe_content.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/swipe_content.rs @@ -3,7 +3,7 @@ use crate::{ ui::{ component::{ base::{AttachType, EventPropagation}, - Component, Event, EventCtx, + Component, Event, EventCtx, PaginateFull, }, constant::screen, display::Color, @@ -11,7 +11,7 @@ use crate::{ geometry::{Direction, Offset, Rect}, lerp::Lerp, shape::{self, Renderer}, - util::animation_disabled, + util::{animation_disabled, Pager}, }, }; @@ -284,6 +284,16 @@ impl Component for SwipeContent { } } +impl PaginateFull for SwipeContent { + fn pager(&self) -> Pager { + self.inner.pager() + } + + fn change_page(&mut self, to_page: u16) { + self.inner.change_page(to_page); + } +} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for SwipeContent where @@ -295,15 +305,9 @@ where } } -pub trait InternallySwipable { - fn current_page(&self) -> usize; - - fn num_pages(&self) -> usize; -} - pub struct InternallySwipableContent where - T: Component + InternallySwipable, + T: Component + PaginateFull, { content: SwipeContent, animate: bool, @@ -311,7 +315,7 @@ where impl InternallySwipableContent where - T: Component + InternallySwipable, + T: Component + PaginateFull, { pub fn new(content: T) -> Self { Self { @@ -325,10 +329,7 @@ where } fn should_animate_attach(&self, attach_type: AttachType) -> bool { - let is_first_page = self.content.inner.current_page() == 0; - let is_last_page = - self.content.inner.current_page() == (self.content.inner.num_pages() - 1); - + let pager = self.content.inner.pager(); let is_swipe_up = matches!(attach_type, AttachType::Swipe(Direction::Up)); let is_swipe_down = matches!(attach_type, AttachType::Swipe(Direction::Down)); @@ -336,11 +337,11 @@ where return false; } - if is_first_page && is_swipe_up { + if pager.is_first() && is_swipe_up { return true; } - if is_last_page && is_swipe_down { + if pager.is_last() && is_swipe_down { return true; } @@ -348,18 +349,15 @@ where } fn should_animate_swipe(&self, swipe_direction: Direction) -> bool { - let is_first_page = self.content.inner.current_page() == 0; - let is_last_page = - self.content.inner.current_page() == (self.content.inner.num_pages() - 1); - + let pager = self.content.inner.pager(); let is_swipe_up = matches!(swipe_direction, Direction::Up); let is_swipe_down = matches!(swipe_direction, Direction::Down); - if is_last_page && is_swipe_up { + if pager.is_last() && is_swipe_up { return true; } - if is_first_page && is_swipe_down { + if pager.is_first() && is_swipe_down { return true; } @@ -369,7 +367,7 @@ where impl Component for InternallySwipableContent where - T: Component + InternallySwipable, + T: Component + PaginateFull, { type Msg = T::Msg; @@ -395,10 +393,23 @@ where } } +impl PaginateFull for InternallySwipableContent +where + T: Component + PaginateFull, +{ + fn pager(&self) -> Pager { + self.content.pager() + } + + fn change_page(&mut self, to_page: u16) { + self.content.change_page(to_page); + } +} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for InternallySwipableContent where - T: crate::trace::Trace + Component + InternallySwipable, + T: crate::trace::Trace + Component + PaginateFull, { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.component("InternallySwipableContent"); diff --git a/core/embed/rust/src/ui/layout_delizia/component/updatable_more_info.rs b/core/embed/rust/src/ui/layout_delizia/component/updatable_more_info.rs index 1e3c14c87c..74d567f10e 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/updatable_more_info.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/updatable_more_info.rs @@ -2,7 +2,7 @@ use crate::{ strutil::TString, ui::{ component::{ - paginated::Paginate, + paginated::SinglePage, text::paragraphs::{Paragraph, Paragraphs}, Component, Event, EventCtx, Never, }, @@ -66,6 +66,8 @@ where } } +impl TString<'static>> SinglePage for UpdatableMoreInfo {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for UpdatableMoreInfo where diff --git a/core/embed/rust/src/ui/layout_delizia/component/vertical_menu.rs b/core/embed/rust/src/ui/layout_delizia/component/vertical_menu.rs index f2d1639b33..a3ea836164 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/vertical_menu.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/vertical_menu.rs @@ -6,20 +6,21 @@ use crate::{ ui::{ component::{ base::{AttachType, Component}, - Event, EventCtx, Paginate, + paginated::SinglePage, + Event, EventCtx, PaginateFull, }, constant::screen, display::{Color, Icon}, geometry::{Direction, Offset, Rect}, lerp::Lerp, shape::{Bar, Renderer}, - util::animation_disabled, + util::{animation_disabled, Pager}, }, }; use super::{ super::component::button::{Button, ButtonContent, ButtonMsg, IconText}, - theme, InternallySwipable, + theme, }; pub enum VerticalMenuChoiceMsg { @@ -303,6 +304,8 @@ impl Component for VerticalMenu { } } +impl SinglePage for VerticalMenu {} + #[cfg(feature = "ui_debug")] impl crate::trace::Trace for VerticalMenu { fn trace(&self, t: &mut dyn crate::trace::Tracer) { @@ -317,14 +320,14 @@ impl crate::trace::Trace for VerticalMenu { // Polymorphic struct, avoid adding code as it gets duplicated, prefer // extending VerticalMenu instead. -pub struct PagedVerticalMenu TString<'static>> { +pub struct PagedVerticalMenu TString<'static>> { inner: VerticalMenu, - page: usize, + page: u16, item_count: usize, label_fn: F, } -impl TString<'static>> PagedVerticalMenu { +impl TString<'static>> PagedVerticalMenu { pub fn new(item_count: usize, label_fn: F) -> Self { let mut result = Self { inner: VerticalMenu::select_word(["".into(), "".into(), "".into()]), @@ -337,20 +340,24 @@ impl TString<'static>> PagedVerticalMenu { } } -impl TString<'static>> Paginate for PagedVerticalMenu { - fn page_count(&self) -> usize { - self.num_pages() +impl TString<'static>> PaginateFull for PagedVerticalMenu { + fn pager(&self) -> Pager { + let num_pages = + (self.item_count / self.inner.n_items) + (self.item_count % self.inner.n_items).min(1); + Pager::new(num_pages as u16).with_current(self.page) } - fn change_page(&mut self, active_page: usize) { - for b in 0..self.inner.n_items { - let i = active_page * self.inner.n_items + b; - let text = if i < self.item_count { + fn change_page(&mut self, active_page: u16) { + let n_items = self.inner.n_items as u16; + for b in 0..n_items { + let i = active_page * n_items + b; + let text = if i < self.item_count as u16 { (self.label_fn)(i) } else { "".into() }; let mut dummy_ctx = EventCtx::new(); + let b = b as usize; self.inner.buttons[b].enable_if(&mut dummy_ctx, !text.is_empty()); self.inner.buttons[b].set_content(ButtonContent::Text(text)); } @@ -359,7 +366,7 @@ impl TString<'static>> Paginate for PagedVerticalMenu { } } -impl TString<'static>> Component for PagedVerticalMenu { +impl TString<'static>> Component for PagedVerticalMenu { type Msg = VerticalMenuChoiceMsg; fn place(&mut self, bounds: Rect) -> Rect { @@ -370,7 +377,7 @@ impl TString<'static>> Component for PagedVerticalMenu { let msg = self.inner.event(ctx, event); if let Some(VerticalMenuChoiceMsg::Selected(i)) = msg { return Some(VerticalMenuChoiceMsg::Selected( - self.inner.n_items * self.page + i, + self.inner.n_items * self.page as usize + i, )); } msg @@ -381,18 +388,8 @@ impl TString<'static>> Component for PagedVerticalMenu { } } -impl TString<'static>> InternallySwipable for PagedVerticalMenu { - fn current_page(&self) -> usize { - self.page - } - - fn num_pages(&self) -> usize { - (self.item_count / self.inner.n_items) + (self.item_count % self.inner.n_items).min(1) - } -} - #[cfg(feature = "ui_debug")] -impl TString<'static>> crate::trace::Trace for PagedVerticalMenu { +impl TString<'static>> crate::trace::Trace for PagedVerticalMenu { fn trace(&self, t: &mut dyn crate::trace::Tracer) { self.inner.trace(t) } diff --git a/core/embed/rust/src/ui/layout_delizia/component_msg_obj.rs b/core/embed/rust/src/ui/layout_delizia/component_msg_obj.rs index 966b9c0cd7..84f067870f 100644 --- a/core/embed/rust/src/ui/layout_delizia/component_msg_obj.rs +++ b/core/embed/rust/src/ui/layout_delizia/component_msg_obj.rs @@ -11,6 +11,7 @@ use crate::{ micropython::obj::Obj, ui::{ component::{ + paginated::PaginateFull, text::paragraphs::{ParagraphSource, Paragraphs}, Component, Never, Timeout, }, @@ -71,7 +72,7 @@ where impl ComponentMsgObj for Frame where - T: ComponentMsgObj, + T: ComponentMsgObj + PaginateFull, { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { match msg { diff --git a/core/embed/rust/src/ui/layout_delizia/flow/confirm_action.rs b/core/embed/rust/src/ui/layout_delizia/flow/confirm_action.rs index 73385fae7c..27726263bf 100644 --- a/core/embed/rust/src/ui/layout_delizia/flow/confirm_action.rs +++ b/core/embed/rust/src/ui/layout_delizia/flow/confirm_action.rs @@ -9,7 +9,7 @@ use crate::{ component::{ swipe_detect::SwipeSettings, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt}, - Component, ComponentExt, EventCtx, Paginate, + Component, ComponentExt, EventCtx, PaginateFull, }, flow::{ base::{Decision, DecisionBuilder as _}, @@ -234,7 +234,7 @@ pub fn new_confirm_action( } #[inline(never)] -fn new_confirm_action_uni( +fn new_confirm_action_uni( content: SwipeContent>, extra: ConfirmActionExtra, strings: ConfirmActionStrings, @@ -266,14 +266,12 @@ fn new_confirm_action_uni( } if page_counter { - fn footer_update_fn( + fn footer_update_fn( content: &SwipeContent>, ctx: &mut EventCtx, footer: &mut Footer, ) { - let current_page = content.inner().current_page(); - let page_count = content.inner().page_count(); - footer.update_page_counter(ctx, current_page, page_count); + footer.update_pager(ctx, content.inner().pager()); } content = content @@ -412,12 +410,12 @@ fn create_confirm( } #[inline(never)] -pub fn new_confirm_action_simple( +pub fn new_confirm_action_simple( content: T, extra: ConfirmActionExtra, strings: ConfirmActionStrings, hold: bool, - page_limit: Option, + page_limit: Option, frame_margin: usize, page_counter: bool, ) -> Result { diff --git a/core/embed/rust/src/ui/layout_delizia/flow/confirm_fido.rs b/core/embed/rust/src/ui/layout_delizia/flow/confirm_fido.rs index 5811db63a2..5a6be5d9b6 100644 --- a/core/embed/rust/src/ui/layout_delizia/flow/confirm_fido.rs +++ b/core/embed/rust/src/ui/layout_delizia/flow/confirm_fido.rs @@ -5,6 +5,7 @@ use crate::{ translations::TR, ui::{ component::{ + paginated::PaginateFull as _, swipe_detect::SwipeSettings, text::paragraphs::{Paragraph, Paragraphs}, ComponentExt, EventCtx, @@ -19,8 +20,8 @@ use crate::{ use super::super::{ component::{ - FidoCredential, Footer, Frame, FrameMsg, InternallySwipable, PagedVerticalMenu, PromptMsg, - PromptScreen, SwipeContent, VerticalMenu, VerticalMenuChoiceMsg, + FidoCredential, Footer, Frame, FrameMsg, PagedVerticalMenu, PromptMsg, PromptScreen, + SwipeContent, VerticalMenu, VerticalMenuChoiceMsg, }, theme, }; @@ -89,13 +90,11 @@ impl FlowController for ConfirmFido { } fn footer_update_fn( - content: &SwipeContent TString<'static>>>>, + content: &SwipeContent TString<'static>>>>, ctx: &mut EventCtx, footer: &mut Footer, ) { - let current_page = content.inner().inner().current_page(); - let total_pages = content.inner().inner().num_pages(); - footer.update_page_counter(ctx, current_page, total_pages); + footer.update_pager(ctx, content.inner().inner().pager()); } fn single_cred() -> bool { @@ -128,8 +127,8 @@ pub fn new_confirm_fido( // Closure to lazy-load the information on given page index. // Done like this to allow arbitrarily many pages without // the need of any allocation here in Rust. - let label_fn = move |page_index| { - let account = unwrap!(accounts.get(page_index)); + let label_fn = move |page_index: u16| { + let account = unwrap!(accounts.get(page_index as usize)); account .try_into() .unwrap_or_else(|_| TString::from_str("-")) diff --git a/core/embed/rust/src/ui/layout_delizia/flow/continue_recovery_homepage.rs b/core/embed/rust/src/ui/layout_delizia/flow/continue_recovery_homepage.rs index 8dbdfe73b2..889869da5e 100644 --- a/core/embed/rust/src/ui/layout_delizia/flow/continue_recovery_homepage.rs +++ b/core/embed/rust/src/ui/layout_delizia/flow/continue_recovery_homepage.rs @@ -10,7 +10,7 @@ use crate::{ text::paragraphs::{ Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, Paragraphs, VecExt, }, - ComponentExt, EventCtx, + ComponentExt, EventCtx, PaginateFull as _, }, flow::{ base::{Decision, DecisionBuilder as _}, @@ -147,11 +147,7 @@ fn footer_update_fn( ctx: &mut EventCtx, footer: &mut Footer, ) { - // FIXME: current_page is implemented for Paragraphs and we have to use Vec::len - // to get total pages instead of using Paginate because it borrows mutably - let current_page = content.inner().inner().current_page(); - let total_pages = content.inner().inner().inner().len() / 2; // 2 paragraphs per page - footer.update_page_counter(ctx, current_page, total_pages); + footer.update_pager(ctx, content.inner().inner().pager()); } pub fn new_continue_recovery_homepage( diff --git a/core/embed/rust/src/ui/layout_delizia/flow/show_share_words.rs b/core/embed/rust/src/ui/layout_delizia/flow/show_share_words.rs index 4a4cfbfdcf..5fcd0b395d 100644 --- a/core/embed/rust/src/ui/layout_delizia/flow/show_share_words.rs +++ b/core/embed/rust/src/ui/layout_delizia/flow/show_share_words.rs @@ -7,7 +7,7 @@ use crate::{ component::{ swipe_detect::SwipeSettings, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs}, - ButtonRequestExt, ComponentExt, EventCtx, + ButtonRequestExt, ComponentExt, EventCtx, PaginateFull as _, }, flow::{ base::{Decision, DecisionBuilder as _}, @@ -20,8 +20,8 @@ use heapless::Vec; use super::super::{ component::{ - Footer, Frame, FrameMsg, Header, InternallySwipable, InternallySwipableContent, - PromptScreen, ShareWords, SwipeContent, + Footer, Frame, FrameMsg, Header, InternallySwipableContent, PromptScreen, ShareWords, + SwipeContent, }, theme, }; @@ -74,9 +74,7 @@ fn footer_updating_func( ctx: &mut EventCtx, footer: &mut Footer, ) { - let current_page = content.inner().current_page(); - let total_pages = content.inner().num_pages(); - footer.update_page_counter(ctx, current_page, total_pages); + footer.update_pager(ctx, content.inner().pager()); } pub fn new_show_share_words( diff --git a/core/embed/rust/src/ui/layout_delizia/flow/util.rs b/core/embed/rust/src/ui/layout_delizia/flow/util.rs index aad51e5361..093ff8e506 100644 --- a/core/embed/rust/src/ui/layout_delizia/flow/util.rs +++ b/core/embed/rust/src/ui/layout_delizia/flow/util.rs @@ -48,7 +48,7 @@ pub struct ConfirmValue { chunkify: bool, text_mono: bool, page_counter: bool, - page_limit: Option, + page_limit: Option, swipe_up: bool, swipe_down: bool, swipe_right: bool, @@ -189,7 +189,7 @@ impl ConfirmValue { self } - pub const fn with_page_limit(mut self, page_limit: Option) -> Self { + pub const fn with_page_limit(mut self, page_limit: Option) -> Self { self.page_limit = page_limit; self }