mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-27 06:42:02 +00:00
feat(core/rust): delizia hotfix - make footer act as a swipe-up button
We introduce a new variant FlowMsg::Next, used only internally (for now). Sending FlowMsg::Next indicates we want to proceed to the next screen of the flow. If there is internal pagination, Next will play a simulated swipe to the child component.
This commit is contained in:
parent
51e796ee30
commit
f0746e44fe
1
core/.changelog.d/4571.changed
Normal file
1
core/.changelog.d/4571.changed
Normal file
@ -0,0 +1 @@
|
|||||||
|
Changed "swipe to continue" to "tap to continue". Screens still respond to swipe-up, but the preferred interaction method is now tapping the lower part of the screen.
|
@ -602,6 +602,7 @@ pub enum FlowMsg {
|
|||||||
Confirmed,
|
Confirmed,
|
||||||
Cancelled,
|
Cancelled,
|
||||||
Info,
|
Info,
|
||||||
|
Next,
|
||||||
Choice(usize),
|
Choice(usize),
|
||||||
Text(ShortString),
|
Text(ShortString),
|
||||||
}
|
}
|
||||||
@ -614,7 +615,7 @@ impl TryFrom<FlowMsg> for crate::micropython::obj::Obj {
|
|||||||
use crate::ui::layout::result;
|
use crate::ui::layout::result;
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
FlowMsg::Confirmed => Ok(result::CONFIRMED.as_obj()),
|
FlowMsg::Confirmed | FlowMsg::Next => Ok(result::CONFIRMED.as_obj()),
|
||||||
FlowMsg::Cancelled => Ok(result::CANCELLED.as_obj()),
|
FlowMsg::Cancelled => Ok(result::CANCELLED.as_obj()),
|
||||||
FlowMsg::Info => Ok(result::INFO.as_obj()),
|
FlowMsg::Info => Ok(result::INFO.as_obj()),
|
||||||
FlowMsg::Choice(i) => i.try_into(),
|
FlowMsg::Choice(i) => i.try_into(),
|
||||||
|
@ -178,10 +178,20 @@ impl SwipeFlow {
|
|||||||
fn handle_event_child(&mut self, ctx: &mut EventCtx, event: Event) -> Decision {
|
fn handle_event_child(&mut self, ctx: &mut EventCtx, event: Event) -> Decision {
|
||||||
let msg = self.current_page_mut().event(ctx, event);
|
let msg = self.current_page_mut().event(ctx, event);
|
||||||
|
|
||||||
if let Some(msg) = msg {
|
match msg {
|
||||||
self.state.handle_event(msg)
|
// HOTFIX: if no decision was reached, AND the result is a next event,
|
||||||
} else {
|
// use the decision for a swipe-up.
|
||||||
Decision::Nothing
|
Some(FlowMsg::Next)
|
||||||
|
if self
|
||||||
|
.current_page()
|
||||||
|
.get_swipe_config()
|
||||||
|
.is_allowed(Direction::Up) =>
|
||||||
|
{
|
||||||
|
self.state.handle_swipe(Direction::Up)
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(msg) => self.state.handle_event(msg),
|
||||||
|
None => Decision::Nothing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
strutil::TString,
|
strutil::TString,
|
||||||
ui::{
|
ui::{
|
||||||
component::{text::TextStyle, Component, Event, EventCtx, Never},
|
component::{text::TextStyle, Component, Event, EventCtx},
|
||||||
display::{Color, Font},
|
display::{Color, Font},
|
||||||
event::SwipeEvent,
|
event::SwipeEvent,
|
||||||
geometry::{Alignment, Alignment2D, Direction, Offset, Point, Rect},
|
geometry::{Alignment, Alignment2D, Direction, Offset, Point, Rect},
|
||||||
lerp::Lerp,
|
lerp::Lerp,
|
||||||
shape::{self, Renderer, Text},
|
shape::{self, Renderer, Text},
|
||||||
|
util::Pager,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{super::fonts::FONT_SUB, theme};
|
use super::{super::fonts::FONT_SUB, theme, Button, ButtonMsg};
|
||||||
|
|
||||||
/// Component showing a task instruction, e.g. "Swipe up", and an optional
|
/// Component showing a task instruction, e.g. "Swipe up", and an optional
|
||||||
/// content consisting of one of these:
|
/// content consisting of one of these:
|
||||||
@ -19,7 +20,6 @@ use super::{super::fonts::FONT_SUB, theme};
|
|||||||
/// A host of this component is responsible of providing the exact area
|
/// A host of this component is responsible of providing the exact area
|
||||||
/// considering also the spacing. The height must be 18px (only instruction) or
|
/// considering also the spacing. The height must be 18px (only instruction) or
|
||||||
/// 37px (instruction and description/position).
|
/// 37px (instruction and description/position).
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Footer<'a> {
|
pub struct Footer<'a> {
|
||||||
area: Rect,
|
area: Rect,
|
||||||
content: FooterContent<'a>,
|
content: FooterContent<'a>,
|
||||||
@ -27,6 +27,7 @@ pub struct Footer<'a> {
|
|||||||
swipe_allow_down: bool,
|
swipe_allow_down: bool,
|
||||||
progress: i16,
|
progress: i16,
|
||||||
dir: Direction,
|
dir: Direction,
|
||||||
|
virtual_button: Button,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -54,6 +55,7 @@ impl<'a> Footer<'a> {
|
|||||||
swipe_allow_up: false,
|
swipe_allow_up: false,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
dir: Direction::Up,
|
dir: Direction::Up,
|
||||||
|
virtual_button: Button::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,8 +86,7 @@ impl<'a> Footer<'a> {
|
|||||||
description_last,
|
description_last,
|
||||||
instruction,
|
instruction,
|
||||||
instruction_last,
|
instruction_last,
|
||||||
page_curr: 0,
|
pager: Pager::single_page(),
|
||||||
page_num: 1,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,18 +113,18 @@ impl<'a> Footer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_page_counter(&mut self, ctx: &mut EventCtx, current: usize, max: usize) {
|
pub fn update_pager(&mut self, ctx: &mut EventCtx, pager: Pager) {
|
||||||
match &mut self.content {
|
match &mut self.content {
|
||||||
FooterContent::PageCounter(counter) => {
|
FooterContent::PageCounter(counter) => {
|
||||||
counter.update_current_page(current, max);
|
counter.update(pager);
|
||||||
self.swipe_allow_down = counter.is_first_page();
|
self.swipe_allow_down = pager.is_first();
|
||||||
self.swipe_allow_up = counter.is_last_page();
|
self.swipe_allow_up = pager.is_last();
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
}
|
}
|
||||||
FooterContent::PageHint(hint) => {
|
FooterContent::PageHint(hint) => {
|
||||||
hint.update_current_page(current, max);
|
hint.update(pager);
|
||||||
self.swipe_allow_down = hint.is_first_page();
|
self.swipe_allow_down = pager.is_first();
|
||||||
self.swipe_allow_up = hint.is_last_page();
|
self.swipe_allow_up = pager.is_last();
|
||||||
ctx.request_paint();
|
ctx.request_paint();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@ -153,15 +154,17 @@ impl<'a> Footer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Component for Footer<'a> {
|
impl<'a> Component for Footer<'a> {
|
||||||
type Msg = Never;
|
type Msg = ();
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
assert!(bounds.height() == self.content.height());
|
assert!(bounds.height() == self.content.height());
|
||||||
self.area = bounds;
|
self.area = bounds;
|
||||||
|
self.virtual_button.place(bounds);
|
||||||
self.area
|
self.area
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
let btn_event = self.virtual_button.event(ctx, event);
|
||||||
match event {
|
match event {
|
||||||
Event::Attach(_) => {
|
Event::Attach(_) => {
|
||||||
self.progress = 0;
|
self.progress = 0;
|
||||||
@ -180,7 +183,10 @@ impl<'a> Component for Footer<'a> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
None
|
match btn_event {
|
||||||
|
Some(ButtonMsg::Clicked) => Some(()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
@ -320,44 +326,33 @@ impl<'a> FooterContent<'a> {
|
|||||||
struct PageCounter {
|
struct PageCounter {
|
||||||
pub instruction: TString<'static>,
|
pub instruction: TString<'static>,
|
||||||
font: Font,
|
font: Font,
|
||||||
page_curr: u8,
|
pager: Pager,
|
||||||
page_max: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageCounter {
|
impl PageCounter {
|
||||||
fn new(instruction: TString<'static>) -> Self {
|
fn new(instruction: TString<'static>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
instruction,
|
instruction,
|
||||||
page_curr: 0,
|
pager: Pager::single_page(),
|
||||||
page_max: 0,
|
|
||||||
font: FONT_SUB,
|
font: FONT_SUB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_current_page(&mut self, new_value: usize, max: usize) {
|
fn update(&mut self, pager: Pager) {
|
||||||
self.page_max = max as u8;
|
self.pager = pager;
|
||||||
self.page_curr = (new_value as u8).clamp(0, self.page_max.saturating_sub(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_first_page(&self) -> bool {
|
|
||||||
self.page_curr == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_last_page(&self) -> bool {
|
|
||||||
self.page_curr + 1 == self.page_max
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageCounter {
|
impl PageCounter {
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) {
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) {
|
||||||
let color = if self.is_last_page() {
|
let color = if self.pager.is_last() {
|
||||||
theme::GREEN_LIGHT
|
theme::GREEN_LIGHT
|
||||||
} else {
|
} else {
|
||||||
theme::GREY_LIGHT
|
theme::GREY_LIGHT
|
||||||
};
|
};
|
||||||
|
|
||||||
let string_curr = uformat!("{}", self.page_curr + 1);
|
let string_curr = uformat!("{}", self.pager.current() + 1);
|
||||||
let string_max = uformat!("{}", self.page_max);
|
let string_max = uformat!("{}", self.pager.total());
|
||||||
|
|
||||||
// center the whole counter "x / yz"
|
// center the whole counter "x / yz"
|
||||||
let offset_x = Offset::x(4); // spacing between foreslash and numbers
|
let offset_x = Offset::x(4); // spacing between foreslash and numbers
|
||||||
@ -396,8 +391,8 @@ impl PageCounter {
|
|||||||
impl crate::trace::Trace for PageCounter {
|
impl crate::trace::Trace for PageCounter {
|
||||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||||
t.component("PageCounter");
|
t.component("PageCounter");
|
||||||
t.int("page current", self.page_curr.into());
|
t.int("page current", self.pager.current().into());
|
||||||
t.int("page max", self.page_max.into());
|
t.int("page max", self.pager.total().into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,36 +402,18 @@ struct PageHint {
|
|||||||
pub description_last: TString<'static>,
|
pub description_last: TString<'static>,
|
||||||
pub instruction: TString<'static>,
|
pub instruction: TString<'static>,
|
||||||
pub instruction_last: TString<'static>,
|
pub instruction_last: TString<'static>,
|
||||||
pub page_curr: u8,
|
pub pager: Pager,
|
||||||
pub page_num: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageHint {
|
impl PageHint {
|
||||||
fn update_current_page(&mut self, current: usize, max: usize) {
|
fn update(&mut self, pager: Pager) {
|
||||||
self.page_num = max as u8;
|
self.pager = pager;
|
||||||
self.page_curr = (current as u8).clamp(0, self.page_num.saturating_sub(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_max_page(&mut self, max: usize) {
|
|
||||||
self.page_num = max as u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_single_page(&self) -> bool {
|
|
||||||
self.page_num <= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_first_page(&self) -> bool {
|
|
||||||
self.page_curr == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_last_page(&self) -> bool {
|
|
||||||
self.page_curr + 1 == self.page_num
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn description(&self) -> TString<'static> {
|
fn description(&self) -> TString<'static> {
|
||||||
if self.is_single_page() {
|
if self.pager.is_single() {
|
||||||
TString::empty()
|
TString::empty()
|
||||||
} else if self.is_last_page() {
|
} else if self.pager.is_last() {
|
||||||
self.description_last
|
self.description_last
|
||||||
} else {
|
} else {
|
||||||
self.description
|
self.description
|
||||||
@ -444,9 +421,9 @@ impl PageHint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn instruction(&self) -> TString<'static> {
|
fn instruction(&self) -> TString<'static> {
|
||||||
if self.is_single_page() {
|
if self.pager.is_single() {
|
||||||
TString::empty()
|
TString::empty()
|
||||||
} else if self.is_last_page() {
|
} else if self.pager.is_last() {
|
||||||
self.instruction_last
|
self.instruction_last
|
||||||
} else {
|
} else {
|
||||||
self.instruction
|
self.instruction
|
||||||
|
@ -3,17 +3,20 @@ use crate::{
|
|||||||
strutil::TString,
|
strutil::TString,
|
||||||
ui::{
|
ui::{
|
||||||
component::{
|
component::{
|
||||||
|
base::AttachType,
|
||||||
|
paginated::PaginateFull,
|
||||||
swipe_detect::{SwipeConfig, SwipeSettings},
|
swipe_detect::{SwipeConfig, SwipeSettings},
|
||||||
text::TextStyle,
|
text::TextStyle,
|
||||||
Component,
|
Component,
|
||||||
Event::{self, Swipe},
|
Event::{self, Swipe},
|
||||||
EventCtx, FlowMsg, SwipeDetect,
|
EventCtx, FlowMsg, MsgMap, SwipeDetect,
|
||||||
},
|
},
|
||||||
display::{Color, Icon},
|
display::{Color, Icon},
|
||||||
event::SwipeEvent,
|
event::SwipeEvent,
|
||||||
geometry::{Alignment, Direction, Insets, Point, Rect},
|
geometry::{Alignment, Direction, Insets, Point, Rect},
|
||||||
lerp::Lerp,
|
lerp::Lerp,
|
||||||
shape::{self, Renderer},
|
shape::{self, Renderer},
|
||||||
|
util::Pager,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,7 +91,6 @@ pub struct Frame<T> {
|
|||||||
footer: Option<Footer<'static>>,
|
footer: Option<Footer<'static>>,
|
||||||
footer_update_fn: Option<fn(&T, &mut EventCtx, &mut Footer)>,
|
footer_update_fn: Option<fn(&T, &mut EventCtx, &mut Footer)>,
|
||||||
swipe: SwipeConfig,
|
swipe: SwipeConfig,
|
||||||
internal_page_cnt: usize,
|
|
||||||
horizontal_swipe: HorizontalSwipe,
|
horizontal_swipe: HorizontalSwipe,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
}
|
}
|
||||||
@ -100,7 +102,7 @@ pub enum FrameMsg<T> {
|
|||||||
|
|
||||||
impl<T> Frame<T>
|
impl<T> Frame<T>
|
||||||
where
|
where
|
||||||
T: Component,
|
T: Component + PaginateFull,
|
||||||
{
|
{
|
||||||
pub const fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self {
|
pub const fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -111,7 +113,6 @@ where
|
|||||||
footer: None,
|
footer: None,
|
||||||
footer_update_fn: None,
|
footer_update_fn: None,
|
||||||
swipe: SwipeConfig::new(),
|
swipe: SwipeConfig::new(),
|
||||||
internal_page_cnt: 1,
|
|
||||||
horizontal_swipe: HorizontalSwipe::new(),
|
horizontal_swipe: HorizontalSwipe::new(),
|
||||||
margin: 0,
|
margin: 0,
|
||||||
}
|
}
|
||||||
@ -277,11 +278,28 @@ where
|
|||||||
self.margin = margin;
|
self.margin = margin;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn map_to_button_msg(self) -> MsgMap<Self, fn(FrameMsg<T::Msg>) -> Option<FlowMsg>> {
|
||||||
|
MsgMap::new(self, |msg| match msg {
|
||||||
|
FrameMsg::Button(b) => Some(b),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(
|
||||||
|
self,
|
||||||
|
func: impl Fn(T::Msg) -> Option<FlowMsg>,
|
||||||
|
) -> MsgMap<Self, impl Fn(FrameMsg<T::Msg>) -> Option<FlowMsg>> {
|
||||||
|
MsgMap::new(self, move |msg| match msg {
|
||||||
|
FrameMsg::Content(c) => func(c),
|
||||||
|
FrameMsg::Button(b) => Some(b),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Component for Frame<T>
|
impl<T> Component for Frame<T>
|
||||||
where
|
where
|
||||||
T: Component,
|
T: Component + PaginateFull,
|
||||||
{
|
{
|
||||||
type Msg = FrameMsg<T::Msg>;
|
type Msg = FrameMsg<T::Msg>;
|
||||||
|
|
||||||
@ -299,7 +317,6 @@ where
|
|||||||
&mut self.horizontal_swipe,
|
&mut self.horizontal_swipe,
|
||||||
self.swipe,
|
self.swipe,
|
||||||
&mut self.header,
|
&mut self.header,
|
||||||
&mut self.footer,
|
|
||||||
ctx,
|
ctx,
|
||||||
event,
|
event,
|
||||||
) {
|
) {
|
||||||
@ -307,14 +324,30 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let msg = self.content.event(ctx, event).map(FrameMsg::Content);
|
let msg = self.content.event(ctx, event).map(FrameMsg::Content);
|
||||||
if let Some(count) = ctx.page_count() {
|
|
||||||
self.internal_page_cnt = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.is_some() {
|
if msg.is_some() {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle footer click as a swipe-up event for internal pagination
|
||||||
|
if let Some(()) = self.footer.event(ctx, event) {
|
||||||
|
// if internal pagination is available, send a swipe up event
|
||||||
|
if !self.content.pager().is_last() {
|
||||||
|
// swipe up
|
||||||
|
let none = self
|
||||||
|
.content
|
||||||
|
.event(ctx, Event::Swipe(SwipeEvent::End(Direction::Up)));
|
||||||
|
assert!(none.is_none());
|
||||||
|
// attach event which triggers the animation
|
||||||
|
let none = self
|
||||||
|
.content
|
||||||
|
.event(ctx, Event::Attach(AttachType::Swipe(Direction::Up)));
|
||||||
|
assert!(none.is_none());
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
return Some(FrameMsg::Button(FlowMsg::Next));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(header_update_fn) = self.header_update_fn {
|
if let Some(header_update_fn) = self.header_update_fn {
|
||||||
header_update_fn(&self.content, ctx, &mut self.header);
|
header_update_fn(&self.content, ctx, &mut self.header);
|
||||||
}
|
}
|
||||||
@ -342,16 +375,11 @@ fn frame_event(
|
|||||||
horizontal_swipe: &mut HorizontalSwipe,
|
horizontal_swipe: &mut HorizontalSwipe,
|
||||||
swipe_config: SwipeConfig,
|
swipe_config: SwipeConfig,
|
||||||
header: &mut Header,
|
header: &mut Header,
|
||||||
footer: &mut Option<Footer>,
|
|
||||||
ctx: &mut EventCtx,
|
ctx: &mut EventCtx,
|
||||||
event: Event,
|
event: Event,
|
||||||
) -> Option<FlowMsg> {
|
) -> Option<FlowMsg> {
|
||||||
// horizontal_swipe does not return any message
|
// horizontal_swipe does not return any message
|
||||||
horizontal_swipe.event(event, swipe_config);
|
horizontal_swipe.event(event, swipe_config);
|
||||||
// msg type of footer is Never, so this should never return a value
|
|
||||||
let none = footer.event(ctx, event);
|
|
||||||
debug_assert!(none.is_none());
|
|
||||||
|
|
||||||
// msg type of header is FlowMsg, which will be the return value
|
// msg type of header is FlowMsg, which will be the return value
|
||||||
header.event(ctx, event)
|
header.event(ctx, event)
|
||||||
}
|
}
|
||||||
@ -382,13 +410,13 @@ fn frame_place(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "micropython")]
|
#[cfg(feature = "micropython")]
|
||||||
impl<T> crate::ui::flow::Swipable for Frame<T> {
|
impl<T: PaginateFull> crate::ui::flow::Swipable for Frame<T> {
|
||||||
fn get_swipe_config(&self) -> SwipeConfig {
|
fn get_swipe_config(&self) -> SwipeConfig {
|
||||||
self.swipe
|
self.swipe
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_internal_page_count(&self) -> usize {
|
fn get_pager(&self) -> Pager {
|
||||||
self.internal_page_cnt
|
self.content.pager()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user