1
0
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:
matejcik 2025-02-01 13:46:58 +01:00 committed by Vít Obrusník
parent 51e796ee30
commit f0746e44fe
5 changed files with 100 additions and 83 deletions

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

View File

@ -602,6 +602,7 @@ pub enum FlowMsg {
Confirmed,
Cancelled,
Info,
Next,
Choice(usize),
Text(ShortString),
}
@ -614,7 +615,7 @@ impl TryFrom<FlowMsg> for crate::micropython::obj::Obj {
use crate::ui::layout::result;
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::Info => Ok(result::INFO.as_obj()),
FlowMsg::Choice(i) => i.try_into(),

View File

@ -178,10 +178,20 @@ impl SwipeFlow {
fn handle_event_child(&mut self, ctx: &mut EventCtx, event: Event) -> Decision {
let msg = self.current_page_mut().event(ctx, event);
if let Some(msg) = msg {
self.state.handle_event(msg)
} else {
Decision::Nothing
match msg {
// HOTFIX: if no decision was reached, AND the result is a next event,
// use the decision for a swipe-up.
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,
}
}

View File

@ -1,16 +1,17 @@
use crate::{
strutil::TString,
ui::{
component::{text::TextStyle, Component, Event, EventCtx, Never},
component::{text::TextStyle, Component, Event, EventCtx},
display::{Color, Font},
event::SwipeEvent,
geometry::{Alignment, Alignment2D, Direction, Offset, Point, Rect},
lerp::Lerp,
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
/// 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
/// considering also the spacing. The height must be 18px (only instruction) or
/// 37px (instruction and description/position).
#[derive(Clone)]
pub struct Footer<'a> {
area: Rect,
content: FooterContent<'a>,
@ -27,6 +27,7 @@ pub struct Footer<'a> {
swipe_allow_down: bool,
progress: i16,
dir: Direction,
virtual_button: Button,
}
#[derive(Clone)]
@ -54,6 +55,7 @@ impl<'a> Footer<'a> {
swipe_allow_up: false,
progress: 0,
dir: Direction::Up,
virtual_button: Button::empty(),
}
}
@ -84,8 +86,7 @@ impl<'a> Footer<'a> {
description_last,
instruction,
instruction_last,
page_curr: 0,
page_num: 1,
pager: Pager::single_page(),
}))
}
@ -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 {
FooterContent::PageCounter(counter) => {
counter.update_current_page(current, max);
self.swipe_allow_down = counter.is_first_page();
self.swipe_allow_up = counter.is_last_page();
counter.update(pager);
self.swipe_allow_down = pager.is_first();
self.swipe_allow_up = pager.is_last();
ctx.request_paint();
}
FooterContent::PageHint(hint) => {
hint.update_current_page(current, max);
self.swipe_allow_down = hint.is_first_page();
self.swipe_allow_up = hint.is_last_page();
hint.update(pager);
self.swipe_allow_down = pager.is_first();
self.swipe_allow_up = pager.is_last();
ctx.request_paint();
}
_ => {
@ -153,15 +154,17 @@ impl<'a> Footer<'a> {
}
impl<'a> Component for Footer<'a> {
type Msg = Never;
type Msg = ();
fn place(&mut self, bounds: Rect) -> Rect {
assert!(bounds.height() == self.content.height());
self.area = bounds;
self.virtual_button.place(bounds);
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 {
Event::Attach(_) => {
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>) {
@ -320,44 +326,33 @@ impl<'a> FooterContent<'a> {
struct PageCounter {
pub instruction: TString<'static>,
font: Font,
page_curr: u8,
page_max: u8,
pager: Pager,
}
impl PageCounter {
fn new(instruction: TString<'static>) -> Self {
Self {
instruction,
page_curr: 0,
page_max: 0,
pager: Pager::single_page(),
font: FONT_SUB,
}
}
fn update_current_page(&mut self, new_value: usize, max: usize) {
self.page_max = max as u8;
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
fn update(&mut self, pager: Pager) {
self.pager = pager;
}
}
impl PageCounter {
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
} else {
theme::GREY_LIGHT
};
let string_curr = uformat!("{}", self.page_curr + 1);
let string_max = uformat!("{}", self.page_max);
let string_curr = uformat!("{}", self.pager.current() + 1);
let string_max = uformat!("{}", self.pager.total());
// center the whole counter "x / yz"
let offset_x = Offset::x(4); // spacing between foreslash and numbers
@ -396,8 +391,8 @@ impl PageCounter {
impl crate::trace::Trace for PageCounter {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("PageCounter");
t.int("page current", self.page_curr.into());
t.int("page max", self.page_max.into());
t.int("page current", self.pager.current().into());
t.int("page max", self.pager.total().into());
}
}
@ -407,36 +402,18 @@ struct PageHint {
pub description_last: TString<'static>,
pub instruction: TString<'static>,
pub instruction_last: TString<'static>,
pub page_curr: u8,
pub page_num: u8,
pub pager: Pager,
}
impl PageHint {
fn update_current_page(&mut self, current: usize, max: usize) {
self.page_num = max as u8;
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 update(&mut self, pager: Pager) {
self.pager = pager;
}
fn description(&self) -> TString<'static> {
if self.is_single_page() {
if self.pager.is_single() {
TString::empty()
} else if self.is_last_page() {
} else if self.pager.is_last() {
self.description_last
} else {
self.description
@ -444,9 +421,9 @@ impl PageHint {
}
fn instruction(&self) -> TString<'static> {
if self.is_single_page() {
if self.pager.is_single() {
TString::empty()
} else if self.is_last_page() {
} else if self.pager.is_last() {
self.instruction_last
} else {
self.instruction

View File

@ -3,17 +3,20 @@ use crate::{
strutil::TString,
ui::{
component::{
base::AttachType,
paginated::PaginateFull,
swipe_detect::{SwipeConfig, SwipeSettings},
text::TextStyle,
Component,
Event::{self, Swipe},
EventCtx, FlowMsg, SwipeDetect,
EventCtx, FlowMsg, MsgMap, SwipeDetect,
},
display::{Color, Icon},
event::SwipeEvent,
geometry::{Alignment, Direction, Insets, Point, Rect},
lerp::Lerp,
shape::{self, Renderer},
util::Pager,
},
};
@ -88,7 +91,6 @@ pub struct Frame<T> {
footer: Option<Footer<'static>>,
footer_update_fn: Option<fn(&T, &mut EventCtx, &mut Footer)>,
swipe: SwipeConfig,
internal_page_cnt: usize,
horizontal_swipe: HorizontalSwipe,
margin: usize,
}
@ -100,7 +102,7 @@ pub enum FrameMsg<T> {
impl<T> Frame<T>
where
T: Component,
T: Component + PaginateFull,
{
pub const fn new(alignment: Alignment, title: TString<'static>, content: T) -> Self {
Self {
@ -111,7 +113,6 @@ where
footer: None,
footer_update_fn: None,
swipe: SwipeConfig::new(),
internal_page_cnt: 1,
horizontal_swipe: HorizontalSwipe::new(),
margin: 0,
}
@ -277,11 +278,28 @@ where
self.margin = margin;
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>
where
T: Component,
T: Component + PaginateFull,
{
type Msg = FrameMsg<T::Msg>;
@ -299,7 +317,6 @@ where
&mut self.horizontal_swipe,
self.swipe,
&mut self.header,
&mut self.footer,
ctx,
event,
) {
@ -307,14 +324,30 @@ where
}
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() {
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 {
header_update_fn(&self.content, ctx, &mut self.header);
}
@ -342,16 +375,11 @@ fn frame_event(
horizontal_swipe: &mut HorizontalSwipe,
swipe_config: SwipeConfig,
header: &mut Header,
footer: &mut Option<Footer>,
ctx: &mut EventCtx,
event: Event,
) -> Option<FlowMsg> {
// horizontal_swipe does not return any message
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
header.event(ctx, event)
}
@ -382,13 +410,13 @@ fn frame_place(
}
#[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 {
self.swipe
}
fn get_internal_page_count(&self) -> usize {
self.internal_page_cnt
fn get_pager(&self) -> Pager {
self.content.pager()
}
}