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,
|
||||
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(),
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user