From f0746e44fe1eb558db3637f4f9b10d59cd162a1a Mon Sep 17 00:00:00 2001 From: matejcik Date: Sat, 1 Feb 2025 13:46:58 +0100 Subject: [PATCH] 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. --- core/.changelog.d/4571.changed | 1 + core/embed/rust/src/ui/component/base.rs | 3 +- core/embed/rust/src/ui/flow/swipe.rs | 18 +++- .../src/ui/layout_delizia/component/footer.rs | 97 +++++++------------ .../src/ui/layout_delizia/component/frame.rs | 64 ++++++++---- 5 files changed, 100 insertions(+), 83 deletions(-) create mode 100644 core/.changelog.d/4571.changed diff --git a/core/.changelog.d/4571.changed b/core/.changelog.d/4571.changed new file mode 100644 index 0000000000..ece94a671f --- /dev/null +++ b/core/.changelog.d/4571.changed @@ -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. diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index 9b24c44fd1..7939d8d531 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -602,6 +602,7 @@ pub enum FlowMsg { Confirmed, Cancelled, Info, + Next, Choice(usize), Text(ShortString), } @@ -614,7 +615,7 @@ impl TryFrom 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(), diff --git a/core/embed/rust/src/ui/flow/swipe.rs b/core/embed/rust/src/ui/flow/swipe.rs index aa4211d02e..1626f27f70 100644 --- a/core/embed/rust/src/ui/flow/swipe.rs +++ b/core/embed/rust/src/ui/flow/swipe.rs @@ -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, } } diff --git a/core/embed/rust/src/ui/layout_delizia/component/footer.rs b/core/embed/rust/src/ui/layout_delizia/component/footer.rs index b1bd9033b4..69c8e9a13c 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/footer.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/footer.rs @@ -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 { + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { + 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 diff --git a/core/embed/rust/src/ui/layout_delizia/component/frame.rs b/core/embed/rust/src/ui/layout_delizia/component/frame.rs index 33a8e1e383..58d0f893ba 100644 --- a/core/embed/rust/src/ui/layout_delizia/component/frame.rs +++ b/core/embed/rust/src/ui/layout_delizia/component/frame.rs @@ -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 { footer: Option>, footer_update_fn: Option, swipe: SwipeConfig, - internal_page_cnt: usize, horizontal_swipe: HorizontalSwipe, margin: usize, } @@ -100,7 +102,7 @@ pub enum FrameMsg { impl Frame 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) -> Option> { + MsgMap::new(self, |msg| match msg { + FrameMsg::Button(b) => Some(b), + _ => None, + }) + } + + pub fn map( + self, + func: impl Fn(T::Msg) -> Option, + ) -> MsgMap) -> Option> { + MsgMap::new(self, move |msg| match msg { + FrameMsg::Content(c) => func(c), + FrameMsg::Button(b) => Some(b), + }) + } } impl Component for Frame where - T: Component, + T: Component + PaginateFull, { type Msg = FrameMsg; @@ -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