1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-02-22 04:22:07 +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()
}
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()
}
}

View File

@ -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) {

View File

@ -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()
}
}

View File

@ -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")]

View File

@ -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<T> {
@ -20,9 +22,43 @@ pub enum PageMsg<T> {
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<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,
};
use super::paginated::SinglePage;
pub struct GridPlaced<T> {
inner: T,
grid: Grid,
@ -272,6 +274,8 @@ where
}
}
impl<T, U> SinglePage for Split<T, U> {}
#[cfg(feature = "ui_debug")]
impl<T, U> crate::trace::Trace for Split<T, U>
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 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) {

View File

@ -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(),
}
}
}

View File

@ -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<Option<LayoutFit>> = 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 { .. })));

View File

@ -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<T> {
offset: PageOffset,
visible: Vec<TextLayoutProxy, MAX_LINES>,
source: T,
pager: Pager,
}
impl<'a, T> Paragraphs<T>
@ -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<T> {
fn break_pages_from(&self, offset: Option<PageOffset>) -> PageBreakIterator<T> {
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<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
/// 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<T>
impl<'a, T> PaginateFull for Paragraphs<T>
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<Rect>,
layout: Option<TextLayoutProxy>,
}
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<Rect>, Option<TextLayoutProxy>) {
) -> 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(&paragraph, &next_paragraph, area, full_height)
{
if Self::should_place_pair_on_next_page(
&paragraph,
&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<T>
where
T: ParagraphSource<'a>,
{
fn page_count(&self) -> usize {
1
}
fn change_page(&mut self, _to_page: usize) {}
}
impl<T> SinglePage for Checklist<T> {}
#[cfg(feature = "ui_debug")]
impl<'a, T: ParagraphSource<'a>> crate::trace::Trace for Checklist<T> {

View File

@ -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.

View File

@ -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<T> {
inner: T,
bounds: Rect,
axis: Axis,
pages: usize,
current: usize,
limit: Option<usize>,
pager: Pager,
limit: Option<u16>,
}
impl<T: Component + Paginate> SwipePage<T> {
impl<T: Component + PaginateFull> SwipePage<T> {
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<T: Component + Paginate> SwipePage<T> {
inner,
bounds: Rect::zero(),
axis: Axis::Horizontal,
pages: 1,
current: 0,
pager: Pager::single_page(),
limit: None,
}
}
@ -43,55 +41,47 @@ impl<T: Component + Paginate> SwipePage<T> {
&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
}
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;
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<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 {
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<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")]
impl<T> crate::trace::Trace for SwipePage<T>
where

View File

@ -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<Decision>,
@ -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() {

View File

@ -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);

View File

@ -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<Option<LayoutFit>> = 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));
});
}

View File

@ -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<Paragraphs<Paragraph<'static>>>,
xpubs: Vec<(TString<'static>, TString<'static>), MAX_XPUBS>,
xpub_page_count: Vec<u8, MAX_XPUBS>,
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<Self::Msg> {
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()
}
}

View File

@ -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<F: Fn() -> TString<'static>> Component for FidoCredential<F> {
}
}
impl<F: Fn() -> TString<'static>> SinglePage for FidoCredential<F> {}
#[cfg(feature = "ui_debug")]
impl<F: Fn() -> TString<'static>> crate::trace::Trace for FidoCredential<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {

View File

@ -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<bool>,
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()
}
}

View File

@ -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,

View File

@ -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")]

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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<u8, MAX_WORDS>;
pub struct ShareWords<'a> {
share_words: Vec<TString<'a>, 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!()
}
}

View File

@ -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) {

View File

@ -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<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")]
impl<T> crate::trace::Trace for SwipeContent<T>
where
@ -295,15 +305,9 @@ where
}
}
pub trait InternallySwipable {
fn current_page(&self) -> usize;
fn num_pages(&self) -> usize;
}
pub struct InternallySwipableContent<T>
where
T: Component + InternallySwipable,
T: Component + PaginateFull,
{
content: SwipeContent<T>,
animate: bool,
@ -311,7 +315,7 @@ where
impl<T> InternallySwipableContent<T>
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<T> Component for InternallySwipableContent<T>
where
T: Component + InternallySwipable,
T: Component + PaginateFull,
{
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")]
impl<T> crate::trace::Trace for InternallySwipableContent<T>
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");

View File

@ -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<F: Fn() -> TString<'static>> SinglePage for UpdatableMoreInfo<F> {}
#[cfg(feature = "ui_debug")]
impl<F> crate::trace::Trace for UpdatableMoreInfo<F>
where

View File

@ -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<F: Fn(usize) -> TString<'static>> {
pub struct PagedVerticalMenu<F: Fn(u16) -> TString<'static>> {
inner: VerticalMenu,
page: usize,
page: u16,
item_count: usize,
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 {
let mut result = Self {
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> {
fn page_count(&self) -> usize {
self.num_pages()
impl<F: Fn(u16) -> TString<'static>> PaginateFull for PagedVerticalMenu<F> {
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<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;
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);
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<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")]
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) {
self.inner.trace(t)
}

View File

@ -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<T> ComponentMsgObj for Frame<T>
where
T: ComponentMsgObj,
T: ComponentMsgObj + PaginateFull,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {

View File

@ -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<T: Component + Paginate + MaybeTrace + 'static>(
fn new_confirm_action_uni<T: Component + PaginateFull + MaybeTrace + 'static>(
content: SwipeContent<SwipePage<T>>,
extra: ConfirmActionExtra,
strings: ConfirmActionStrings,
@ -266,14 +266,12 @@ fn new_confirm_action_uni<T: Component + Paginate + MaybeTrace + 'static>(
}
if page_counter {
fn footer_update_fn<T: Component + Paginate>(
fn footer_update_fn<T: Component + PaginateFull>(
content: &SwipeContent<SwipePage<T>>,
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<T: Component + Paginate + MaybeTrace + 'static>(
pub fn new_confirm_action_simple<T: Component + PaginateFull + MaybeTrace + 'static>(
content: T,
extra: ConfirmActionExtra,
strings: ConfirmActionStrings,
hold: bool,
page_limit: Option<usize>,
page_limit: Option<u16>,
frame_margin: usize,
page_counter: bool,
) -> Result<SwipeFlow, error::Error> {

View File

@ -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<SwipePage<PagedVerticalMenu<impl Fn(usize) -> TString<'static>>>>,
content: &SwipeContent<SwipePage<PagedVerticalMenu<impl Fn(u16) -> 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("-"))

View File

@ -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(

View File

@ -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(

View File

@ -48,7 +48,7 @@ pub struct ConfirmValue {
chunkify: bool,
text_mono: bool,
page_counter: bool,
page_limit: Option<usize>,
page_limit: Option<u16>,
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<usize>) -> Self {
pub const fn with_page_limit(mut self, page_limit: Option<u16>) -> Self {
self.page_limit = page_limit;
self
}