1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-04 05:42:34 +00:00

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
This commit is contained in:
matejcik 2025-01-29 13:36:57 +01:00 committed by obrusvit
parent a02ba87a46
commit 68bda9cee8
34 changed files with 422 additions and 348 deletions

View File

@ -73,8 +73,8 @@ impl<T: crate::ui::flow::Swipable> crate::ui::flow::Swipable for SendButtonReque
self.inner.get_swipe_config() self.inner.get_swipe_config()
} }
fn get_internal_page_count(&self) -> usize { fn get_pager(&self) -> crate::ui::util::Pager {
self.inner.get_internal_page_count() self.inner.get_pager()
} }
} }

View File

@ -9,6 +9,8 @@ use crate::{
}, },
}; };
use super::paginated::SinglePage;
pub struct CachedJpeg { pub struct CachedJpeg {
area: Rect, area: Rect,
image_size: Offset, image_size: Offset,
@ -73,6 +75,8 @@ impl Component for CachedJpeg {
} }
} }
impl SinglePage for CachedJpeg {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for CachedJpeg { impl crate::trace::Trace for CachedJpeg {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -43,8 +43,8 @@ where
fn get_swipe_config(&self) -> SwipeConfig { fn get_swipe_config(&self) -> SwipeConfig {
self.inner.get_swipe_config() self.inner.get_swipe_config()
} }
fn get_internal_page_count(&self) -> usize { fn get_pager(&self) -> crate::ui::util::Pager {
self.inner.get_internal_page_count() self.inner.get_pager()
} }
} }
@ -109,7 +109,7 @@ where
fn get_swipe_config(&self) -> SwipeConfig { fn get_swipe_config(&self) -> SwipeConfig {
self.inner.get_swipe_config() self.inner.get_swipe_config()
} }
fn get_internal_page_count(&self) -> usize { fn get_pager(&self) -> crate::ui::util::Pager {
self.inner.get_internal_page_count() self.inner.get_pager()
} }
} }

View File

@ -48,7 +48,7 @@ pub use map::{MsgMap, PageMap};
pub use marquee::Marquee; pub use marquee::Marquee;
pub use maybe::Maybe; pub use maybe::Maybe;
pub use pad::Pad; pub use pad::Pad;
pub use paginated::{PageMsg, Paginate}; pub use paginated::{PageMsg, Paginate, PaginateFull};
pub use placed::{FixedHeightBar, Floating, GridPlaced, Split}; pub use placed::{FixedHeightBar, Floating, GridPlaced, Split};
pub use qr_code::Qr; pub use qr_code::Qr;
#[cfg(feature = "touch")] #[cfg(feature = "touch")]

View File

@ -1,3 +1,5 @@
use crate::ui::util::Pager;
/// Common message type for pagination components. /// Common message type for pagination components.
#[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))] #[cfg_attr(feature = "debug", derive(ufmt::derive::uDebug))]
pub enum PageMsg<T> { pub enum PageMsg<T> {
@ -20,9 +22,43 @@ pub enum PageMsg<T> {
SwipeRight, SwipeRight,
} }
/// TRANSITIONAL paginate trait that only optionally returns the current page.
/// Use PaginateFull for the new trait that returns a Pager.
pub trait Paginate { pub trait Paginate {
/// How many pages of content are there in total? /// How many pages of content are there in total?
fn page_count(&self) -> usize; fn page_count(&self) -> usize;
/// Navigate to the given page. /// Navigate to the given page.
fn change_page(&mut self, active_page: usize); 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<T: PaginateFull> 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<T: SinglePage> PaginateFull for T {
fn pager(&self) -> Pager {
Pager::single_page()
}
fn change_page(&mut self, active_page: u16) {
if active_page != 0 {
unimplemented!()
}
}
}

View File

@ -4,6 +4,8 @@ use crate::ui::{
shape::Renderer, shape::Renderer,
}; };
use super::paginated::SinglePage;
pub struct GridPlaced<T> { pub struct GridPlaced<T> {
inner: T, inner: T,
grid: Grid, grid: Grid,
@ -272,6 +274,8 @@ where
} }
} }
impl<T, U> SinglePage for Split<T, U> {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Split<T, U> impl<T, U> crate::trace::Trace for Split<T, U>
where where

View File

@ -12,6 +12,8 @@ use crate::{
}, },
}; };
use super::paginated::SinglePage;
const NVERSIONS: usize = 10; // range of versions (=capacities) that we support 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_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]; 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")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Qr { impl crate::trace::Trace for Qr {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -6,7 +6,7 @@ use crate::{
constant::screen, constant::screen,
event::{SwipeEvent, TouchEvent}, event::{SwipeEvent, TouchEvent},
geometry::{Axis, Direction, Offset, Point}, geometry::{Axis, Direction, Offset, Point},
util::animation_disabled, util::{animation_disabled, Pager},
}, },
}; };
@ -106,23 +106,21 @@ impl SwipeConfig {
self self
} }
pub fn with_pagination(mut self, current_page: u16, total_pages: u16) -> Self { pub fn with_pager(mut self, pager: Pager) -> Self {
let has_prev = current_page > 0;
let has_next = current_page < total_pages.saturating_sub(1);
match self.page_axis { match self.page_axis {
Some(Axis::Horizontal) => { Some(Axis::Horizontal) => {
if has_prev { if pager.has_prev() {
self.right = Some(SwipeSettings::default()); self.right = Some(SwipeSettings::default());
} }
if has_next { if pager.has_next() {
self.left = Some(SwipeSettings::default()); self.left = Some(SwipeSettings::default());
} }
} }
Some(Axis::Vertical) => { Some(Axis::Vertical) => {
if has_prev { if pager.has_prev() {
self.down = Some(SwipeSettings::default()); self.down = Some(SwipeSettings::default());
} }
if has_next { if pager.has_next() {
self.up = Some(SwipeSettings::default()); self.up = Some(SwipeSettings::default());
} }
} }
@ -131,15 +129,13 @@ impl SwipeConfig {
self self
} }
pub fn paging_event(&self, dir: Direction, current_page: u16, total_pages: u16) -> u16 { pub fn paging_event(&self, dir: Direction, pager: Pager) -> u16 {
let prev_page = current_page.saturating_sub(1);
let next_page = (current_page + 1).min(total_pages.saturating_sub(1));
match (self.page_axis, dir) { match (self.page_axis, dir) {
(Some(Axis::Horizontal), Direction::Right) => prev_page, (Some(Axis::Horizontal), Direction::Right) => pager.prev(),
(Some(Axis::Horizontal), Direction::Left) => next_page, (Some(Axis::Horizontal), Direction::Left) => pager.next(),
(Some(Axis::Vertical), Direction::Down) => prev_page, (Some(Axis::Vertical), Direction::Down) => pager.prev(),
(Some(Axis::Vertical), Direction::Up) => next_page, (Some(Axis::Vertical), Direction::Up) => pager.next(),
_ => current_page, _ => pager.current(),
} }
} }
} }

View File

@ -1,7 +1,8 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Never, Paginate}, component::{Component, Event, EventCtx, Never, PaginateFull},
geometry::{Alignment, Offset, Rect}, geometry::{Alignment, Offset, Rect},
shape::Renderer, shape::Renderer,
util::Pager,
}; };
use super::{ use super::{
@ -15,6 +16,7 @@ pub struct FormattedText {
vertical: Alignment, vertical: Alignment,
char_offset: usize, char_offset: usize,
y_offset: i16, y_offset: i16,
pager: Pager,
} }
impl FormattedText { impl FormattedText {
@ -24,6 +26,7 @@ impl FormattedText {
vertical: Alignment::Start, vertical: Alignment::Start,
char_offset: 0, char_offset: 0,
y_offset: 0, y_offset: 0,
pager: Pager::single_page(),
} }
} }
@ -32,18 +35,9 @@ impl FormattedText {
self self
} }
pub(crate) fn layout_content(&self, sink: &mut dyn LayoutSink) -> LayoutFit { 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 {
self.op_layout 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) { fn align_vertically(&mut self, content_height: i16) {
@ -58,19 +52,79 @@ impl FormattedText {
Alignment::End => bounds_height - content_height, 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 // Pagination
impl Paginate for FormattedText { impl PaginateFull for FormattedText {
fn page_count(&self) -> usize { 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 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 { 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 { match fit {
LayoutFit::Fitting { .. } => { LayoutFit::Fitting { .. } => {
break; break;
@ -79,50 +133,18 @@ impl Paginate for FormattedText {
processed_chars, .. processed_chars, ..
} => { } => {
page_count += 1; 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; 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 { // reset position to start
type Msg = Never; 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 bounds
} }
@ -140,12 +162,11 @@ impl Component for FormattedText {
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for FormattedText { impl crate::trace::Trace for FormattedText {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
use crate::ui::component::text::layout::trace::TraceSink;
use core::cell::Cell; use core::cell::Cell;
let fit: Cell<Option<LayoutFit>> = Cell::new(None); let fit: Cell<Option<LayoutFit>> = Cell::new(None);
t.component("FormattedText"); t.component("FormattedText");
t.in_list("text", &|l| { 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)); fit.set(Some(result));
}); });
t.bool("fits", matches!(fit.get(), Some(LayoutFit::Fitting { .. }))); t.bool("fits", matches!(fit.get(), Some(LayoutFit::Fitting { .. })));

View File

@ -3,10 +3,11 @@ use heapless::Vec;
use crate::{ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{Component, Event, EventCtx, Never, Paginate}, component::{paginated::SinglePage, Component, Event, EventCtx, Never, PaginateFull},
display::{font::Font, toif::Icon, Color}, display::{font::Font, toif::Icon, Color},
geometry::{Alignment, Dimensions, Insets, LinearPlacement, Offset, Point, Rect}, geometry::{Alignment, Dimensions, Insets, LinearPlacement, Offset, Point, Rect},
shape::{self, Renderer}, shape::{self, Renderer},
util::Pager,
}, },
}; };
@ -51,6 +52,7 @@ pub struct Paragraphs<T> {
offset: PageOffset, offset: PageOffset,
visible: Vec<TextLayoutProxy, MAX_LINES>, visible: Vec<TextLayoutProxy, MAX_LINES>,
source: T, source: T,
pager: Pager,
} }
impl<'a, T> Paragraphs<T> impl<'a, T> Paragraphs<T>
@ -66,6 +68,7 @@ where
offset: PageOffset::default(), offset: PageOffset::default(),
visible: Vec::new(), visible: Vec::new(),
source, source,
pager: Pager::single_page(),
} }
} }
@ -100,12 +103,6 @@ where
result.unwrap_or(self.area) 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 /// Update bounding boxes of paragraphs on the current page. First determine
/// the number of visible paragraphs and their sizes. These are then /// the number of visible paragraphs and their sizes. These are then
/// arranged according to the layout. /// arranged according to the layout.
@ -127,28 +124,42 @@ where
let full_height = area.height(); let full_height = area.height();
while offset.par < source.size() { while offset.par < source.size() {
let (next_offset, remaining_area, layout) = offset.advance(area, source, full_height); let advance = offset.advance(area, source, full_height);
if let Some(layout) = layout { if let Some(layout) = advance.layout {
unwrap!(visible.push(layout)); unwrap!(visible.push(layout));
} }
if let Some(remaining_area) = remaining_area { if let Some(remaining_area) = advance.remaining_area {
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
assert_eq!(next_offset.par, offset.par + 1); assert_eq!(advance.offset.par, offset.par + 1);
area = remaining_area; area = remaining_area;
offset = next_offset; offset = advance.offset;
} else { } else {
break; break;
} }
} }
} }
fn break_pages(&self) -> PageBreakIterator<T> { fn break_pages_from(&self, offset: Option<PageOffset>) -> PageBreakIterator<T> {
PageBreakIterator { PageBreakIterator {
paragraphs: self, 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<T> {
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<T> {
self.break_pages_from(Some(self.offset))
}
/// Iterate over visible layouts (bounding box, style) together /// Iterate over visible layouts (bounding box, style) together
/// with corresponding string content. Should not get monomorphized. /// with corresponding string content. Should not get monomorphized.
fn foreach_visible<'b>( fn foreach_visible<'b>(
@ -184,7 +195,13 @@ where
type Msg = Never; type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
let recalc = self.area != bounds;
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.change_offset(self.offset);
self.area self.area
} }
@ -205,22 +222,32 @@ where
} }
} }
impl<'a, T> Paginate for Paragraphs<T> impl<'a, T> PaginateFull for Paragraphs<T>
where where
T: ParagraphSource<'a>, T: ParagraphSource<'a>,
{ {
fn page_count(&self) -> usize { fn pager(&self) -> Pager {
// There's always at least one page. self.pager
self.break_pages().count().max(1)
} }
fn change_page(&mut self, to_page: usize) { fn change_page(&mut self, to_page: u16) {
if let Some(offset) = self.break_pages().nth(to_page) { use core::cmp::Ordering;
self.change_offset(offset)
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 { } else {
// Should not happen, set index to first paragraph and render empty page. // Should not happen, set index to first paragraph and render empty page.
self.offset = PageOffset::default(); 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 { struct PageOffset {
/// Index of paragraph. /// Index of paragraph.
par: usize, par: usize,
@ -372,6 +399,13 @@ struct PageOffset {
chr: usize, chr: usize,
} }
/// Helper struct for `PageOffset::advance`
struct PageOffsetAdvance {
offset: PageOffset,
remaining_area: Option<Rect>,
layout: Option<TextLayoutProxy>,
}
impl PageOffset { impl PageOffset {
/// Given a `PageOffset` and a `Rect` area, returns: /// Given a `PageOffset` and a `Rect` area, returns:
/// ///
@ -388,29 +422,34 @@ impl PageOffset {
area: Rect, area: Rect,
source: &dyn ParagraphSource<'_>, source: &dyn ParagraphSource<'_>,
full_height: i16, full_height: i16,
) -> (PageOffset, Option<Rect>, Option<TextLayoutProxy>) { ) -> PageOffsetAdvance {
let paragraph = source.at(self.par, self.chr); let paragraph = source.at(self.par, self.chr);
// Skip empty paragraphs. // Skip empty paragraphs.
if paragraph.content().is_empty() { if paragraph.content().is_empty() {
self.par += 1; self.par += 1;
self.chr = 0; 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. // Handle the `no_break` flag used to keep key-value pair on the same page:
if paragraph.no_break && self.chr == 0 { // * no_break is set
if let Some(next_paragraph) = // * we're at the start of a paragraph ("key")
(self.par + 1 < source.size()).then(|| source.at(self.par + 1, 0)) // * 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(&paragraph, &next_paragraph, area, full_height)
{ {
if Self::should_place_pair_on_next_page( return PageOffsetAdvance {
&paragraph, offset: self,
&next_paragraph, remaining_area: None,
area, layout: None,
full_height, };
) {
return (self, None, None);
}
} }
} }
@ -441,11 +480,11 @@ impl PageOffset {
} }
} }
( PageOffsetAdvance {
self, offset: self,
Some(remaining_area).filter(|_| !page_full), remaining_area: (!page_full).then_some(remaining_area),
Some(layout).filter(|_| fit.height() > 0), layout: (fit.height() > 0).then_some(layout),
) }
} }
fn should_place_pair_on_next_page( fn should_place_pair_on_next_page(
@ -505,18 +544,17 @@ impl<'a, T: ParagraphSource<'a>> PageBreakIterator<'_, T> {
let full_height = area.height(); let full_height = area.height();
while offset.par < paragraphs.size() { while offset.par < paragraphs.size() {
let (next_offset, remaining_area, _layout) = let advance = offset.advance(area, paragraphs, full_height);
offset.advance(area, paragraphs, full_height); if advance.offset.par >= paragraphs.size() {
if next_offset.par >= paragraphs.size() {
// Last page. // Last page.
return None; return None;
} else if let Some(remaining_area) = remaining_area { } else if let Some(remaining_area) = advance.remaining_area {
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
assert_eq!(next_offset.par, offset.par + 1); assert_eq!(advance.offset.par, offset.par + 1);
area = remaining_area; area = remaining_area;
offset = next_offset; offset = advance.offset;
} else { } else {
return Some(next_offset); return Some(advance.offset);
} }
} }
@ -693,16 +731,7 @@ where
} }
} }
impl<'a, T> Paginate for Checklist<T> impl<T> SinglePage for Checklist<T> {}
where
T: ParagraphSource<'a>,
{
fn page_count(&self) -> usize {
1
}
fn change_page(&mut self, _to_page: usize) {}
}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Checklist<T> { impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Checklist<T> {

View File

@ -1,6 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{base::AttachType, swipe_detect::SwipeConfig}, component::{base::AttachType, swipe_detect::SwipeConfig},
geometry::Direction, geometry::Direction,
util::Pager,
}; };
pub use crate::ui::component::FlowMsg; pub use crate::ui::component::FlowMsg;
@ -8,7 +9,7 @@ pub use crate::ui::component::FlowMsg;
pub trait Swipable { pub trait Swipable {
fn get_swipe_config(&self) -> SwipeConfig; fn get_swipe_config(&self) -> SwipeConfig;
fn get_internal_page_count(&self) -> usize; fn get_pager(&self) -> Pager;
} }
/// Composable event handler result. /// Composable event handler result.

View File

@ -1,8 +1,9 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Paginate}, component::{Component, Event, EventCtx, PaginateFull},
event::SwipeEvent, event::SwipeEvent,
geometry::{Axis, Direction, Rect}, geometry::{Axis, Direction, Rect},
shape::Renderer, shape::Renderer,
util::Pager,
}; };
/// Allows any implementor of `Paginate` to be part of `Swipable` UI flow. /// Allows any implementor of `Paginate` to be part of `Swipable` UI flow.
@ -11,19 +12,17 @@ pub struct SwipePage<T> {
inner: T, inner: T,
bounds: Rect, bounds: Rect,
axis: Axis, axis: Axis,
pages: usize, pager: Pager,
current: usize, limit: Option<u16>,
limit: Option<usize>,
} }
impl<T: Component + Paginate> SwipePage<T> { impl<T: Component + PaginateFull> SwipePage<T> {
pub fn vertical(inner: T) -> Self { pub fn vertical(inner: T) -> Self {
Self { Self {
inner, inner,
bounds: Rect::zero(), bounds: Rect::zero(),
axis: Axis::Vertical, axis: Axis::Vertical,
pages: 1, pager: Pager::single_page(),
current: 0,
limit: None, limit: None,
} }
} }
@ -33,8 +32,7 @@ impl<T: Component + Paginate> SwipePage<T> {
inner, inner,
bounds: Rect::zero(), bounds: Rect::zero(),
axis: Axis::Horizontal, axis: Axis::Horizontal,
pages: 1, pager: Pager::single_page(),
current: 0,
limit: None, limit: None,
} }
} }
@ -43,55 +41,47 @@ impl<T: Component + Paginate> SwipePage<T> {
&self.inner &self.inner
} }
pub fn with_limit(mut self, limit: Option<usize>) -> Self { pub fn with_limit(mut self, limit: Option<u16>) -> Self {
self.limit = limit; self.limit = limit;
self self
} }
pub fn page_count(&self) -> usize {
self.pages
}
pub fn current_page(&self) -> usize {
self.current
}
} }
impl<T: Component + Paginate> Component for SwipePage<T> { impl<T: Component + PaginateFull> Component for SwipePage<T> {
type Msg = T::Msg; type Msg = T::Msg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
self.bounds = self.inner.place(bounds); self.bounds = self.inner.place(bounds);
self.pages = self.inner.page_count(); self.pager = self.inner.pager();
if let Some(limit) = self.limit { if let Some(limit) = self.limit {
self.pages = self.pages.min(limit); self.pager = self.pager.with_limit(limit);
} }
self.bounds self.bounds
} }
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
ctx.set_page_count(self.pages); ctx.set_page_count(self.pager.total() as usize);
if let Event::Swipe(SwipeEvent::End(direction)) = event { if let Event::Swipe(SwipeEvent::End(direction)) = event {
match (self.axis, direction) { match (self.axis, direction) {
(Axis::Vertical, Direction::Up) => { (Axis::Vertical, Direction::Up) => {
self.current = (self.current + 1).min(self.pages - 1); self.pager.goto_next();
self.inner.change_page(self.current); self.inner.change_page(self.pager.current());
ctx.request_paint(); ctx.request_paint();
} }
(Axis::Vertical, Direction::Down) => { (Axis::Vertical, Direction::Down) => {
self.current = self.current.saturating_sub(1); self.pager.goto_prev();
self.inner.change_page(self.current); self.inner.change_page(self.pager.current());
ctx.request_paint(); ctx.request_paint();
} }
(Axis::Horizontal, Direction::Left) => { (Axis::Horizontal, Direction::Left) => {
self.current = (self.current + 1).min(self.pages - 1); self.pager.goto_next();
self.inner.change_page(self.current); self.inner.change_page(self.pager.current());
ctx.request_paint(); ctx.request_paint();
} }
(Axis::Horizontal, Direction::Right) => { (Axis::Horizontal, Direction::Right) => {
self.current = self.current.saturating_sub(1); self.pager.goto_prev();
self.inner.change_page(self.current); self.inner.change_page(self.pager.current());
ctx.request_paint(); ctx.request_paint();
} }
_ => {} _ => {}
@ -106,6 +96,17 @@ impl<T: Component + Paginate> Component for SwipePage<T> {
} }
} }
impl<T: PaginateFull> PaginateFull for SwipePage<T> {
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")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for SwipePage<T> impl<T> crate::trace::Trace for SwipePage<T>
where where

View File

@ -103,10 +103,6 @@ pub struct SwipeFlow {
swipe: SwipeDetect, swipe: SwipeDetect,
/// Swipe allowed /// Swipe allowed
allow_swipe: bool, 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 /// If triggering swipe by event, make this decision instead of default
/// after the swipe. /// after the swipe.
pending_decision: Option<Decision>, pending_decision: Option<Decision>,
@ -123,8 +119,6 @@ impl SwipeFlow {
swipe: SwipeDetect::new(), swipe: SwipeDetect::new(),
store: Vec::new(), store: Vec::new(),
allow_swipe: true, allow_swipe: true,
internal_page_idx: 0,
internal_pages: 1,
pending_decision: None, pending_decision: None,
lifecycle_state: LayoutState::Initial, lifecycle_state: LayoutState::Initial,
returned_value: None, returned_value: None,
@ -154,19 +148,6 @@ impl SwipeFlow {
&mut self.store[self.state.index()] &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. /// Transition to a different state.
/// ///
/// This is the only way to change the current flow state. /// This is the only way to change the current flow state.
@ -183,7 +164,6 @@ impl SwipeFlow {
self.current_page_mut() self.current_page_mut()
.event(ctx, Event::Attach(attach_type)); .event(ctx, Event::Attach(attach_type));
self.update_page_count(attach_type);
ctx.request_paint(); ctx.request_paint();
} }
@ -209,27 +189,20 @@ impl SwipeFlow {
let mut decision = Decision::Nothing; let mut decision = Decision::Nothing;
let mut return_transition: AttachType = AttachType::Initial; 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 mut attach = false;
let event = if self.allow_swipe { let event = if self.allow_swipe {
let page = self.current_page(); let page = self.current_page();
let config = page let pager = page.get_pager();
.get_swipe_config() let config = page.get_swipe_config().with_pager(pager);
.with_pagination(self.internal_page_idx, self.internal_pages);
match self.swipe.event(ctx, event, config) { match self.swipe.event(ctx, event, config) {
Some(SwipeEvent::End(dir)) => { Some(SwipeEvent::End(dir)) => {
return_transition = AttachType::Swipe(dir); return_transition = AttachType::Swipe(dir);
let new_internal_page_idx = let new_internal_page_idx = config.paging_event(dir, pager);
config.paging_event(dir, self.internal_page_idx, self.internal_pages); if new_internal_page_idx != pager.current() {
if new_internal_page_idx != self.internal_page_idx {
// internal paging event // internal paging event
self.internal_page_idx = new_internal_page_idx;
decision = Decision::Nothing; decision = Decision::Nothing;
attach = true; attach = true;
} else if let Some(override_decision) = self.pending_decision.take() { } else if let Some(override_decision) = self.pending_decision.take() {

View File

@ -121,8 +121,8 @@ where
fn update(&mut self, ctx: &mut EventCtx, get_new_page: bool) { fn update(&mut self, ctx: &mut EventCtx, get_new_page: bool) {
if get_new_page { if get_new_page {
self.change_current_page(ctx); self.change_current_page(ctx);
self.current_page.place(self.content_area);
} }
self.current_page.place(self.content_area);
self.set_buttons(ctx); self.set_buttons(ctx);
self.scrollbar.request_complete_repaint(ctx); self.scrollbar.request_complete_repaint(ctx);
self.clear_and_repaint(ctx); self.clear_and_repaint(ctx);

View File

@ -209,14 +209,10 @@ impl Paginate for Page {
} }
// DEBUG-ONLY SECTION BELOW // DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
use crate::ui::component::text::layout::LayoutFit;
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Page { impl crate::trace::Trace for Page {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { 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; use core::cell::Cell;
let fit: Cell<Option<LayoutFit>> = Cell::new(None); let fit: Cell<Option<LayoutFit>> = Cell::new(None);
t.component("Page"); t.component("Page");
@ -227,7 +223,7 @@ impl crate::trace::Trace for Page {
t.int("active_page", self.current_page as i64); t.int("active_page", self.current_page as i64);
t.int("page_count", self.page_count as i64); t.int("page_count", self.page_count as i64);
t.in_list("text", &|l| { 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)); fit.set(Some(result));
}); });
} }

View File

@ -8,12 +8,13 @@ use crate::{
component::{ component::{
swipe_detect::{SwipeConfig, SwipeSettings}, swipe_detect::{SwipeConfig, SwipeSettings},
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
Component, Event, EventCtx, Paginate, Component, Event, EventCtx, PaginateFull,
}, },
event::SwipeEvent, event::SwipeEvent,
flow::Swipable, flow::Swipable,
geometry::{Direction, Rect}, geometry::{Direction, Rect},
shape::Renderer, shape::Renderer,
util::Pager,
}, },
}; };
@ -26,7 +27,7 @@ pub struct AddressDetails {
xpub_view: Frame<Paragraphs<Paragraph<'static>>>, xpub_view: Frame<Paragraphs<Paragraph<'static>>>,
xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>, xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>,
xpub_page_count: Vec<u8, MAX_XPUBS>, xpub_page_count: Vec<u8, MAX_XPUBS>,
current_page: usize, current_page: u16,
} }
impl AddressDetails { impl AddressDetails {
@ -84,7 +85,7 @@ impl AddressDetails {
.map_err(|_| Error::OutOfRange) .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 // Context is needed for updating child so that it can request repaint. In this
// case the parent component that handles paging always requests complete // case the parent component that handles paging always requests complete
// repaint after page change so we can use a dummy context here. // 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_title(&mut dummy_ctx, self.xpubs[i].0);
self.xpub_view.update_content(&mut dummy_ctx, |_ctx, p| { self.xpub_view.update_content(&mut dummy_ctx, |_ctx, p| {
p.inner_mut().update(self.xpubs[i].1); p.inner_mut().update(self.xpubs[i].1);
let npages = p.page_count(); let npages = p.pager().total() as usize;
p.change_page(page); p.change_page(page);
npages 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_index = 0;
let mut xpub_page = scrollbar_page; let mut xpub_page = scrollbar_page;
for page_count in self.xpub_page_count.iter().map(|pc| { for page_count in self.xpub_page_count.iter() {
let upc: usize = (*pc).into(); let page_count = *page_count as u16;
upc
}) {
if page_count <= xpub_page { if page_count <= xpub_page {
xpub_page -= page_count; xpub_page -= page_count;
xpub_index += 1; xpub_index += 1;
@ -116,12 +115,13 @@ impl AddressDetails {
} }
} }
impl Paginate for AddressDetails { impl PaginateFull for AddressDetails {
fn page_count(&self) -> usize { fn pager(&self) -> Pager {
self.get_internal_page_count() 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; self.current_page = to_page;
if to_page > 0 { if to_page > 0 {
let i = to_page - 1; let i = to_page - 1;
@ -148,17 +148,14 @@ impl Component for AddressDetails {
} }
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
ctx.set_page_count(self.page_count()); ctx.set_page_count(self.pager().total() as usize);
match event { match event {
Event::Swipe(SwipeEvent::End(Direction::Right)) => { 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); self.change_page(to_page);
} }
Event::Swipe(SwipeEvent::End(Direction::Left)) => { Event::Swipe(SwipeEvent::End(Direction::Left)) => {
let to_page = self let to_page = self.pager().next();
.current_page
.saturating_add(1)
.min(self.page_count() - 1);
self.change_page(to_page); self.change_page(to_page);
} }
_ => {} _ => {}
@ -190,9 +187,8 @@ impl Swipable for AddressDetails {
} }
} }
fn get_internal_page_count(&self) -> usize { fn get_pager(&self) -> Pager {
let total_xpub_pages: u8 = self.xpub_page_count.iter().copied().sum(); self.pager()
1usize.saturating_add(total_xpub_pages.into())
} }
} }

View File

@ -3,6 +3,7 @@ use crate::{
ui::{ ui::{
component::{ component::{
image::Image, image::Image,
paginated::SinglePage,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
Component, Event, EventCtx, Component, Event, EventCtx,
}, },
@ -77,6 +78,8 @@ impl<F: Fn() -> TString<'static>> Component for FidoCredential<F> {
} }
} }
impl<F: Fn() -> TString<'static>> SinglePage for FidoCredential<F> {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<F: Fn() -> TString<'static>> crate::trace::Trace for FidoCredential<F> { impl<F: Fn() -> TString<'static>> crate::trace::Trace for FidoCredential<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -8,9 +8,8 @@ use crate::{
}, },
display, display,
geometry::{Alignment, Direction, Grid, Insets, Offset, Rect}, geometry::{Alignment, Direction, Grid, Insets, Offset, Rect},
shape, shape::{self, Renderer},
shape::Renderer, util::{long_line_content_with_ellipsis, Pager},
util::long_line_content_with_ellipsis,
}, },
}; };
@ -88,7 +87,6 @@ pub struct PassphraseKeyboard {
active_layout: KeyboardLayout, active_layout: KeyboardLayout,
fade: Cell<bool>, fade: Cell<bool>,
swipe_config: SwipeConfig, // FIXME: how about page_swipe swipe_config: SwipeConfig, // FIXME: how about page_swipe
internal_page_cnt: usize,
} }
const PAGE_COUNT: usize = 4; const PAGE_COUNT: usize = 4;
@ -159,7 +157,6 @@ impl PassphraseKeyboard {
active_layout, active_layout,
fade: Cell::new(false), fade: Cell::new(false),
swipe_config: SwipeConfig::new(), swipe_config: SwipeConfig::new(),
internal_page_cnt: 1,
} }
} }
@ -481,8 +478,8 @@ impl crate::ui::flow::Swipable for PassphraseKeyboard {
self.swipe_config self.swipe_config
} }
fn get_internal_page_count(&self) -> usize { fn get_pager(&self) -> Pager {
self.internal_page_cnt Pager::single_page()
} }
} }

View File

@ -1,5 +1,5 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx}, component::{paginated::SinglePage, Component, Event, EventCtx},
geometry::{Alignment, Grid, GridCellSpan, Rect}, geometry::{Alignment, Grid, GridCellSpan, Rect},
shape::Renderer, shape::Renderer,
}; };
@ -72,6 +72,8 @@ impl Component for SelectWordCount {
} }
} }
impl SinglePage for SelectWordCount {}
pub struct ValueKeypad { pub struct ValueKeypad {
button: [Button; Self::NUMBERS.len()], button: [Button; Self::NUMBERS.len()],
keypad_area: Rect, keypad_area: Rect,

View File

@ -79,7 +79,7 @@ pub use scroll::ScrollBar;
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
pub use share_words::ShareWords; pub use share_words::ShareWords;
pub use status_screen::StatusScreen; pub use status_screen::StatusScreen;
pub use swipe_content::{InternallySwipable, InternallySwipableContent, SwipeContent}; pub use swipe_content::{InternallySwipableContent, SwipeContent};
#[cfg(feature = "translations")] #[cfg(feature = "translations")]
pub use swipe_up_screen::{SwipeUpScreen, SwipeUpScreenMsg}; pub use swipe_up_screen::{SwipeUpScreen, SwipeUpScreenMsg};
#[cfg(feature = "translations")] #[cfg(feature = "translations")]

View File

@ -3,6 +3,7 @@ use crate::{
strutil::{self, TString}, strutil::{self, TString},
ui::{ ui::{
component::{ component::{
paginated::SinglePage,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
Component, Event, EventCtx, Pad, Component, Event, EventCtx, Pad,
}, },
@ -79,6 +80,8 @@ impl Component for NumberInputDialog {
} }
} }
impl SinglePage for NumberInputDialog {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInputDialog { impl crate::trace::Trace for NumberInputDialog {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -3,7 +3,7 @@ use crate::{
strutil::{ShortString, TString}, strutil::{ShortString, TString},
translations::TR, translations::TR,
ui::{ ui::{
component::{Component, Event, EventCtx}, component::{paginated::SinglePage, Component, Event, EventCtx},
constant::screen, constant::screen,
event::TouchEvent, event::TouchEvent,
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
@ -101,6 +101,8 @@ impl Component for NumberInputSliderDialog {
} }
} }
impl SinglePage for NumberInputSliderDialog {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInputSliderDialog { impl crate::trace::Trace for NumberInputSliderDialog {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -1,5 +1,5 @@
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx}, component::{paginated::SinglePage, Component, Event, EventCtx},
geometry::Rect, geometry::Rect,
shape::Renderer, shape::Renderer,
}; };
@ -115,6 +115,8 @@ impl Component for PromptScreen {
} }
} }
impl SinglePage for PromptScreen {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for PromptScreen { impl crate::trace::Trace for PromptScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -2,19 +2,20 @@ use crate::{
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{base::AttachType, text::TextStyle, Component, Event, EventCtx, Never}, component::{
base::AttachType, paginated::PaginateFull, text::TextStyle, Component, Event, EventCtx,
Never,
},
event::SwipeEvent, event::SwipeEvent,
geometry::{Alignment, Alignment2D, Direction, Insets, Offset, Rect}, geometry::{Alignment, Alignment2D, Direction, Insets, Offset, Rect},
shape::{self, Renderer}, shape::{self, Renderer},
util::Pager,
}, },
}; };
use heapless::Vec; use heapless::Vec;
use super::{ use super::{super::component::swipe_content::SwipeAttachAnimation, theme};
super::component::{swipe_content::SwipeAttachAnimation, InternallySwipable},
theme,
};
const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less
@ -26,8 +27,8 @@ type IndexVec = Vec<u8, MAX_WORDS>;
pub struct ShareWords<'a> { pub struct ShareWords<'a> {
share_words: Vec<TString<'a>, MAX_WORDS>, share_words: Vec<TString<'a>, MAX_WORDS>,
subtitle: TString<'static>, subtitle: TString<'static>,
page_index: i16, page_index: u16,
next_index: i16, next_index: u16,
/// Area reserved for a shown word from mnemonic/share /// Area reserved for a shown word from mnemonic/share
area_word: Rect, area_word: Rect,
progress: i16, progress: i16,
@ -55,11 +56,11 @@ impl<'a> ShareWords<'a> {
} }
fn is_first_page(&self) -> bool { fn is_first_page(&self) -> bool {
self.page_index == 0 self.pager().is_first()
} }
fn is_final_page(&self) -> bool { 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 { fn find_repeated(share_words: &[TString]) -> IndexVec {
@ -85,9 +86,9 @@ impl<'a> ShareWords<'a> {
(self.subtitle, &theme::TEXT_SUB_GREY) (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 // the share word
if word_index >= self.share_words.len() as _ || word_index < 0 { if word_index >= self.share_words.len() as _ {
return; return;
} }
let word = self.share_words[word_index as usize]; 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 { Event::Swipe(SwipeEvent::End(dir)) => match dir {
Direction::Up if !self.is_final_page() => { Direction::Up if !self.is_final_page() => {
self.progress = 0; 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; self.wait_for_attach = true;
ctx.request_paint(); ctx.request_paint();
} }
Direction::Down if !self.is_first_page() => { Direction::Down if !self.is_first_page() => {
self.progress = 0; self.progress = 0;
self.page_index = self.page_index.saturating_sub(1); self.page_index = self.pager().prev();
self.wait_for_attach = true; self.wait_for_attach = true;
ctx.request_paint(); ctx.request_paint();
} }
@ -172,11 +173,11 @@ impl<'a> Component for ShareWords<'a> {
Event::Swipe(SwipeEvent::Move(dir, progress)) => { Event::Swipe(SwipeEvent::Move(dir, progress)) => {
match dir { match dir {
Direction::Up => { Direction::Up => {
self.next_index = self.page_index + 1; self.next_index = self.pager().next();
self.progress = progress; self.progress = progress;
} }
Direction::Down => { Direction::Down => {
self.next_index = self.page_index - 1; self.next_index = self.pager().prev();
self.progress = progress; self.progress = progress;
} }
_ => {} _ => {}
@ -241,13 +242,13 @@ impl<'a> Component for ShareWords<'a> {
} }
} }
impl InternallySwipable for ShareWords<'_> { impl PaginateFull for ShareWords<'_> {
fn current_page(&self) -> usize { fn pager(&self) -> Pager {
self.page_index as usize Pager::new(self.share_words.len() as u16).with_current(self.page_index)
} }
fn num_pages(&self) -> usize { fn change_page(&mut self, active_page: u16) {
self.share_words.len() unimplemented!()
} }
} }

View File

@ -3,13 +3,12 @@ use crate::{
strutil::TString, strutil::TString,
time::{Duration, Stopwatch}, time::{Duration, Stopwatch},
ui::{ ui::{
component::{Component, Event, EventCtx, Label, Timeout}, component::{paginated::SinglePage, Component, Event, EventCtx, Label, Timeout},
constant::screen, constant::screen,
display::{Color, Icon}, display::{Color, Icon},
geometry::{Alignment, Alignment2D, Insets, Point, Rect}, geometry::{Alignment, Alignment2D, Insets, Point, Rect},
lerp::Lerp, lerp::Lerp,
shape, shape::{self, Renderer},
shape::Renderer,
util::animation_disabled, util::animation_disabled,
}, },
}; };
@ -276,6 +275,8 @@ impl Component for StatusScreen {
} }
} }
impl SinglePage for StatusScreen {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for StatusScreen { impl crate::trace::Trace for StatusScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -3,7 +3,7 @@ use crate::{
ui::{ ui::{
component::{ component::{
base::{AttachType, EventPropagation}, base::{AttachType, EventPropagation},
Component, Event, EventCtx, Component, Event, EventCtx, PaginateFull,
}, },
constant::screen, constant::screen,
display::Color, display::Color,
@ -11,7 +11,7 @@ use crate::{
geometry::{Direction, Offset, Rect}, geometry::{Direction, Offset, Rect},
lerp::Lerp, lerp::Lerp,
shape::{self, Renderer}, shape::{self, Renderer},
util::animation_disabled, util::{animation_disabled, Pager},
}, },
}; };
@ -284,6 +284,16 @@ impl<T: Component> Component for SwipeContent<T> {
} }
} }
impl<T: PaginateFull> PaginateFull for SwipeContent<T> {
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")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for SwipeContent<T> impl<T> crate::trace::Trace for SwipeContent<T>
where where
@ -295,15 +305,9 @@ where
} }
} }
pub trait InternallySwipable {
fn current_page(&self) -> usize;
fn num_pages(&self) -> usize;
}
pub struct InternallySwipableContent<T> pub struct InternallySwipableContent<T>
where where
T: Component + InternallySwipable, T: Component + PaginateFull,
{ {
content: SwipeContent<T>, content: SwipeContent<T>,
animate: bool, animate: bool,
@ -311,7 +315,7 @@ where
impl<T> InternallySwipableContent<T> impl<T> InternallySwipableContent<T>
where where
T: Component + InternallySwipable, T: Component + PaginateFull,
{ {
pub fn new(content: T) -> Self { pub fn new(content: T) -> Self {
Self { Self {
@ -325,10 +329,7 @@ where
} }
fn should_animate_attach(&self, attach_type: AttachType) -> bool { fn should_animate_attach(&self, attach_type: AttachType) -> bool {
let is_first_page = self.content.inner.current_page() == 0; let pager = self.content.inner.pager();
let is_last_page =
self.content.inner.current_page() == (self.content.inner.num_pages() - 1);
let is_swipe_up = matches!(attach_type, AttachType::Swipe(Direction::Up)); let is_swipe_up = matches!(attach_type, AttachType::Swipe(Direction::Up));
let is_swipe_down = matches!(attach_type, AttachType::Swipe(Direction::Down)); let is_swipe_down = matches!(attach_type, AttachType::Swipe(Direction::Down));
@ -336,11 +337,11 @@ where
return false; return false;
} }
if is_first_page && is_swipe_up { if pager.is_first() && is_swipe_up {
return true; return true;
} }
if is_last_page && is_swipe_down { if pager.is_last() && is_swipe_down {
return true; return true;
} }
@ -348,18 +349,15 @@ where
} }
fn should_animate_swipe(&self, swipe_direction: Direction) -> bool { fn should_animate_swipe(&self, swipe_direction: Direction) -> bool {
let is_first_page = self.content.inner.current_page() == 0; let pager = self.content.inner.pager();
let is_last_page =
self.content.inner.current_page() == (self.content.inner.num_pages() - 1);
let is_swipe_up = matches!(swipe_direction, Direction::Up); let is_swipe_up = matches!(swipe_direction, Direction::Up);
let is_swipe_down = matches!(swipe_direction, Direction::Down); 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; return true;
} }
if is_first_page && is_swipe_down { if pager.is_first() && is_swipe_down {
return true; return true;
} }
@ -369,7 +367,7 @@ where
impl<T> Component for InternallySwipableContent<T> impl<T> Component for InternallySwipableContent<T>
where where
T: Component + InternallySwipable, T: Component + PaginateFull,
{ {
type Msg = T::Msg; type Msg = T::Msg;
@ -395,10 +393,23 @@ where
} }
} }
impl<T> PaginateFull for InternallySwipableContent<T>
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")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for InternallySwipableContent<T> impl<T> crate::trace::Trace for InternallySwipableContent<T>
where where
T: crate::trace::Trace + Component + InternallySwipable, T: crate::trace::Trace + Component + PaginateFull,
{ {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("InternallySwipableContent"); t.component("InternallySwipableContent");

View File

@ -2,7 +2,7 @@ use crate::{
strutil::TString, strutil::TString,
ui::{ ui::{
component::{ component::{
paginated::Paginate, paginated::SinglePage,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
Component, Event, EventCtx, Never, Component, Event, EventCtx, Never,
}, },
@ -66,6 +66,8 @@ where
} }
} }
impl<F: Fn() -> TString<'static>> SinglePage for UpdatableMoreInfo<F> {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<F> crate::trace::Trace for UpdatableMoreInfo<F> impl<F> crate::trace::Trace for UpdatableMoreInfo<F>
where where

View File

@ -6,20 +6,21 @@ use crate::{
ui::{ ui::{
component::{ component::{
base::{AttachType, Component}, base::{AttachType, Component},
Event, EventCtx, Paginate, paginated::SinglePage,
Event, EventCtx, PaginateFull,
}, },
constant::screen, constant::screen,
display::{Color, Icon}, display::{Color, Icon},
geometry::{Direction, Offset, Rect}, geometry::{Direction, Offset, Rect},
lerp::Lerp, lerp::Lerp,
shape::{Bar, Renderer}, shape::{Bar, Renderer},
util::animation_disabled, util::{animation_disabled, Pager},
}, },
}; };
use super::{ use super::{
super::component::button::{Button, ButtonContent, ButtonMsg, IconText}, super::component::button::{Button, ButtonContent, ButtonMsg, IconText},
theme, InternallySwipable, theme,
}; };
pub enum VerticalMenuChoiceMsg { pub enum VerticalMenuChoiceMsg {
@ -303,6 +304,8 @@ impl Component for VerticalMenu {
} }
} }
impl SinglePage for VerticalMenu {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for VerticalMenu { impl crate::trace::Trace for VerticalMenu {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { 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 // Polymorphic struct, avoid adding code as it gets duplicated, prefer
// extending VerticalMenu instead. // extending VerticalMenu instead.
pub struct PagedVerticalMenu<F: Fn(usize) -> TString<'static>> { pub struct PagedVerticalMenu<F: Fn(u16) -> TString<'static>> {
inner: VerticalMenu, inner: VerticalMenu,
page: usize, page: u16,
item_count: usize, item_count: usize,
label_fn: F, label_fn: F,
} }
impl<F: Fn(usize) -> TString<'static>> PagedVerticalMenu<F> { impl<F: Fn(u16) -> TString<'static>> PagedVerticalMenu<F> {
pub fn new(item_count: usize, label_fn: F) -> Self { pub fn new(item_count: usize, label_fn: F) -> Self {
let mut result = Self { let mut result = Self {
inner: VerticalMenu::select_word(["".into(), "".into(), "".into()]), inner: VerticalMenu::select_word(["".into(), "".into(), "".into()]),
@ -337,20 +340,24 @@ impl<F: Fn(usize) -> TString<'static>> PagedVerticalMenu<F> {
} }
} }
impl<F: Fn(usize) -> TString<'static>> Paginate for PagedVerticalMenu<F> { impl<F: Fn(u16) -> TString<'static>> PaginateFull for PagedVerticalMenu<F> {
fn page_count(&self) -> usize { fn pager(&self) -> Pager {
self.num_pages() 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) { fn change_page(&mut self, active_page: u16) {
for b in 0..self.inner.n_items { let n_items = self.inner.n_items as u16;
let i = active_page * self.inner.n_items + b; for b in 0..n_items {
let text = if i < self.item_count { let i = active_page * n_items + b;
let text = if i < self.item_count as u16 {
(self.label_fn)(i) (self.label_fn)(i)
} else { } else {
"".into() "".into()
}; };
let mut dummy_ctx = EventCtx::new(); 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].enable_if(&mut dummy_ctx, !text.is_empty());
self.inner.buttons[b].set_content(ButtonContent::Text(text)); self.inner.buttons[b].set_content(ButtonContent::Text(text));
} }
@ -359,7 +366,7 @@ impl<F: Fn(usize) -> TString<'static>> Paginate for PagedVerticalMenu<F> {
} }
} }
impl<F: Fn(usize) -> TString<'static>> Component for PagedVerticalMenu<F> { impl<F: Fn(u16) -> TString<'static>> Component for PagedVerticalMenu<F> {
type Msg = VerticalMenuChoiceMsg; type Msg = VerticalMenuChoiceMsg;
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -370,7 +377,7 @@ impl<F: Fn(usize) -> TString<'static>> Component for PagedVerticalMenu<F> {
let msg = self.inner.event(ctx, event); let msg = self.inner.event(ctx, event);
if let Some(VerticalMenuChoiceMsg::Selected(i)) = msg { if let Some(VerticalMenuChoiceMsg::Selected(i)) = msg {
return Some(VerticalMenuChoiceMsg::Selected( return Some(VerticalMenuChoiceMsg::Selected(
self.inner.n_items * self.page + i, self.inner.n_items * self.page as usize + i,
)); ));
} }
msg msg
@ -381,18 +388,8 @@ impl<F: Fn(usize) -> TString<'static>> Component for PagedVerticalMenu<F> {
} }
} }
impl<F: Fn(usize) -> TString<'static>> InternallySwipable for PagedVerticalMenu<F> {
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")] #[cfg(feature = "ui_debug")]
impl<F: Fn(usize) -> TString<'static>> crate::trace::Trace for PagedVerticalMenu<F> { impl<F: Fn(u16) -> TString<'static>> crate::trace::Trace for PagedVerticalMenu<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) { fn trace(&self, t: &mut dyn crate::trace::Tracer) {
self.inner.trace(t) self.inner.trace(t)
} }

View File

@ -11,6 +11,7 @@ use crate::{
micropython::obj::Obj, micropython::obj::Obj,
ui::{ ui::{
component::{ component::{
paginated::PaginateFull,
text::paragraphs::{ParagraphSource, Paragraphs}, text::paragraphs::{ParagraphSource, Paragraphs},
Component, Never, Timeout, Component, Never, Timeout,
}, },
@ -71,7 +72,7 @@ where
impl<T> ComponentMsgObj for Frame<T> impl<T> ComponentMsgObj for Frame<T>
where where
T: ComponentMsgObj, T: ComponentMsgObj + PaginateFull,
{ {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg { match msg {

View File

@ -9,7 +9,7 @@ use crate::{
component::{ component::{
swipe_detect::SwipeSettings, swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt},
Component, ComponentExt, EventCtx, Paginate, Component, ComponentExt, EventCtx, PaginateFull,
}, },
flow::{ flow::{
base::{Decision, DecisionBuilder as _}, base::{Decision, DecisionBuilder as _},
@ -234,7 +234,7 @@ pub fn new_confirm_action(
} }
#[inline(never)] #[inline(never)]
fn new_confirm_action_uni<T: Component + Paginate + MaybeTrace + 'static>( fn new_confirm_action_uni<T: Component + PaginateFull + MaybeTrace + 'static>(
content: SwipeContent<SwipePage<T>>, content: SwipeContent<SwipePage<T>>,
extra: ConfirmActionExtra, extra: ConfirmActionExtra,
strings: ConfirmActionStrings, strings: ConfirmActionStrings,
@ -266,14 +266,12 @@ fn new_confirm_action_uni<T: Component + Paginate + MaybeTrace + 'static>(
} }
if page_counter { if page_counter {
fn footer_update_fn<T: Component + Paginate>( fn footer_update_fn<T: Component + PaginateFull>(
content: &SwipeContent<SwipePage<T>>, content: &SwipeContent<SwipePage<T>>,
ctx: &mut EventCtx, ctx: &mut EventCtx,
footer: &mut Footer, footer: &mut Footer,
) { ) {
let current_page = content.inner().current_page(); footer.update_pager(ctx, content.inner().pager());
let page_count = content.inner().page_count();
footer.update_page_counter(ctx, current_page, page_count);
} }
content = content content = content
@ -412,12 +410,12 @@ fn create_confirm(
} }
#[inline(never)] #[inline(never)]
pub fn new_confirm_action_simple<T: Component + Paginate + MaybeTrace + 'static>( pub fn new_confirm_action_simple<T: Component + PaginateFull + MaybeTrace + 'static>(
content: T, content: T,
extra: ConfirmActionExtra, extra: ConfirmActionExtra,
strings: ConfirmActionStrings, strings: ConfirmActionStrings,
hold: bool, hold: bool,
page_limit: Option<usize>, page_limit: Option<u16>,
frame_margin: usize, frame_margin: usize,
page_counter: bool, page_counter: bool,
) -> Result<SwipeFlow, error::Error> { ) -> Result<SwipeFlow, error::Error> {

View File

@ -5,6 +5,7 @@ use crate::{
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
paginated::PaginateFull as _,
swipe_detect::SwipeSettings, swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, EventCtx, ComponentExt, EventCtx,
@ -19,8 +20,8 @@ use crate::{
use super::super::{ use super::super::{
component::{ component::{
FidoCredential, Footer, Frame, FrameMsg, InternallySwipable, PagedVerticalMenu, PromptMsg, FidoCredential, Footer, Frame, FrameMsg, PagedVerticalMenu, PromptMsg, PromptScreen,
PromptScreen, SwipeContent, VerticalMenu, VerticalMenuChoiceMsg, SwipeContent, VerticalMenu, VerticalMenuChoiceMsg,
}, },
theme, theme,
}; };
@ -89,13 +90,11 @@ impl FlowController for ConfirmFido {
} }
fn footer_update_fn( fn footer_update_fn(
content: &SwipeContent<SwipePage<PagedVerticalMenu<impl Fn(usize) -> TString<'static>>>>, content: &SwipeContent<SwipePage<PagedVerticalMenu<impl Fn(u16) -> TString<'static>>>>,
ctx: &mut EventCtx, ctx: &mut EventCtx,
footer: &mut Footer, footer: &mut Footer,
) { ) {
let current_page = content.inner().inner().current_page(); footer.update_pager(ctx, content.inner().inner().pager());
let total_pages = content.inner().inner().num_pages();
footer.update_page_counter(ctx, current_page, total_pages);
} }
fn single_cred() -> bool { fn single_cred() -> bool {
@ -128,8 +127,8 @@ pub fn new_confirm_fido(
// Closure to lazy-load the information on given page index. // Closure to lazy-load the information on given page index.
// Done like this to allow arbitrarily many pages without // Done like this to allow arbitrarily many pages without
// the need of any allocation here in Rust. // the need of any allocation here in Rust.
let label_fn = move |page_index| { let label_fn = move |page_index: u16| {
let account = unwrap!(accounts.get(page_index)); let account = unwrap!(accounts.get(page_index as usize));
account account
.try_into() .try_into()
.unwrap_or_else(|_| TString::from_str("-")) .unwrap_or_else(|_| TString::from_str("-"))

View File

@ -10,7 +10,7 @@ use crate::{
text::paragraphs::{ text::paragraphs::{
Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, Paragraphs, VecExt, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, Paragraphs, VecExt,
}, },
ComponentExt, EventCtx, ComponentExt, EventCtx, PaginateFull as _,
}, },
flow::{ flow::{
base::{Decision, DecisionBuilder as _}, base::{Decision, DecisionBuilder as _},
@ -147,11 +147,7 @@ fn footer_update_fn(
ctx: &mut EventCtx, ctx: &mut EventCtx,
footer: &mut Footer, footer: &mut Footer,
) { ) {
// FIXME: current_page is implemented for Paragraphs and we have to use Vec::len footer.update_pager(ctx, content.inner().inner().pager());
// 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);
} }
pub fn new_continue_recovery_homepage( pub fn new_continue_recovery_homepage(

View File

@ -7,7 +7,7 @@ use crate::{
component::{ component::{
swipe_detect::SwipeSettings, swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs},
ButtonRequestExt, ComponentExt, EventCtx, ButtonRequestExt, ComponentExt, EventCtx, PaginateFull as _,
}, },
flow::{ flow::{
base::{Decision, DecisionBuilder as _}, base::{Decision, DecisionBuilder as _},
@ -20,8 +20,8 @@ use heapless::Vec;
use super::super::{ use super::super::{
component::{ component::{
Footer, Frame, FrameMsg, Header, InternallySwipable, InternallySwipableContent, Footer, Frame, FrameMsg, Header, InternallySwipableContent, PromptScreen, ShareWords,
PromptScreen, ShareWords, SwipeContent, SwipeContent,
}, },
theme, theme,
}; };
@ -74,9 +74,7 @@ fn footer_updating_func(
ctx: &mut EventCtx, ctx: &mut EventCtx,
footer: &mut Footer, footer: &mut Footer,
) { ) {
let current_page = content.inner().current_page(); footer.update_pager(ctx, content.inner().pager());
let total_pages = content.inner().num_pages();
footer.update_page_counter(ctx, current_page, total_pages);
} }
pub fn new_show_share_words( pub fn new_show_share_words(

View File

@ -48,7 +48,7 @@ pub struct ConfirmValue {
chunkify: bool, chunkify: bool,
text_mono: bool, text_mono: bool,
page_counter: bool, page_counter: bool,
page_limit: Option<usize>, page_limit: Option<u16>,
swipe_up: bool, swipe_up: bool,
swipe_down: bool, swipe_down: bool,
swipe_right: bool, swipe_right: bool,
@ -189,7 +189,7 @@ impl ConfirmValue {
self self
} }
pub const fn with_page_limit(mut self, page_limit: Option<usize>) -> Self { pub const fn with_page_limit(mut self, page_limit: Option<u16>) -> Self {
self.page_limit = page_limit; self.page_limit = page_limit;
self self
} }