diff --git a/core/embed/rust/src/ui/component/map.rs b/core/embed/rust/src/ui/component/map.rs index 76100cbf5..4b171a128 100644 --- a/core/embed/rust/src/ui/component/map.rs +++ b/core/embed/rust/src/ui/component/map.rs @@ -1,6 +1,7 @@ use super::{Component, Event, EventCtx}; use crate::ui::{geometry::Rect, shape::Renderer}; +#[derive(Clone)] pub struct MsgMap { inner: T, func: F, @@ -50,3 +51,17 @@ where self.inner.trace(t) } } + +#[cfg(all(feature = "micropython", feature = "touch"))] +impl crate::ui::flow::Swipable for MsgMap +where + T: Component + crate::ui::flow::Swipable, +{ + fn can_swipe(&self, direction: crate::ui::component::SwipeDirection) -> bool { + self.inner.can_swipe(direction) + } + + fn swiped(&mut self, ctx: &mut EventCtx, direction: crate::ui::component::SwipeDirection) { + self.inner.swiped(ctx, direction) + } +} diff --git a/core/embed/rust/src/ui/flow/base.rs b/core/embed/rust/src/ui/flow/base.rs index c7c827d5d..061f0ac85 100644 --- a/core/embed/rust/src/ui/flow/base.rs +++ b/core/embed/rust/src/ui/flow/base.rs @@ -71,7 +71,7 @@ impl Decision { /// triggered by events and swipes. pub trait FlowState where - Self: Sized + Copy + PartialEq + Eq + ToPrimitive, + Self: Sized + Copy + Eq + ToPrimitive, { /// There needs to be a mapping from states to indices of the FlowStore /// array. Default implementation works for states that are enums, the diff --git a/core/embed/rust/src/ui/flow/flow.rs b/core/embed/rust/src/ui/flow/flow.rs index 7553771b2..29c9c178e 100644 --- a/core/embed/rust/src/ui/flow/flow.rs +++ b/core/embed/rust/src/ui/flow/flow.rs @@ -32,8 +32,11 @@ pub struct SwipeFlow { } struct Transition { + /// State we are transitioning _from_. prev_state: Q, + /// Animation progress. animation: Animation, + /// Direction of the slide animation. direction: SwipeDirection, } @@ -142,6 +145,7 @@ impl Component for SwipeFlow { // TODO: are there any events we want to send to all? timers perhaps? if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event { self.handle_transition(ctx); + return None; } // Ignore events while transition is running. if self.transition.is_some() { diff --git a/core/embed/rust/src/ui/flow/store.rs b/core/embed/rust/src/ui/flow/store.rs index 993170154..cc116f7f9 100644 --- a/core/embed/rust/src/ui/flow/store.rs +++ b/core/embed/rust/src/ui/flow/store.rs @@ -21,8 +21,7 @@ pub trait FlowStore { /// Call `Component::place` on all elements. fn place(&mut self, bounds: Rect) -> Rect; - /// Call `Component::event` on i-th element, if it emits a message it is - /// converted to `FlowMsg` using a function. + /// Call `Component::event` on i-th element. fn event(&mut self, i: usize, ctx: &mut EventCtx, event: Event) -> Option; /// Call `Component::render` on i-th element. @@ -42,10 +41,9 @@ pub trait FlowStore { fn render_cloned<'s>(&'s self, target: &mut impl Renderer<'s>); /// Add a Component to the end of a `FlowStore`. - fn add( + fn add + MaybeTrace + Swipable + Clone>( self, elem: E, - func: fn(E::Msg) -> Option, ) -> Result where Self: Sized; @@ -87,38 +85,33 @@ impl FlowStore for FlowEmpty { } fn render_cloned<'s>(&'s self, _target: &mut impl Renderer<'s>) {} - fn add( + fn add + MaybeTrace + Swipable + Clone>( self, elem: E, - func: fn(E::Msg) -> Option, ) -> Result where Self: Sized, { Ok(FlowComponent { elem: Gc::new(elem)?, - func, cloned: None, next: Self, }) } } -struct FlowComponent { +struct FlowComponent, P> { /// Component allocated on micropython heap. pub elem: Gc, /// Clone. pub cloned: Option>, - /// Function to convert message to `FlowMsg`. - pub func: fn(E::Msg) -> Option, - /// Nested FlowStore. pub next: P, } -impl FlowComponent { +impl, P> FlowComponent { fn as_ref(&self) -> &E { &self.elem } @@ -132,7 +125,7 @@ impl FlowComponent { impl FlowStore for FlowComponent where - E: Component + MaybeTrace + Swipable + Clone, + E: Component + MaybeTrace + Swipable + Clone, P: FlowStore, { fn place(&mut self, bounds: Rect) -> Rect { @@ -143,8 +136,7 @@ where fn event(&mut self, i: usize, ctx: &mut EventCtx, event: Event) -> Option { if i == 0 { - let msg = self.as_mut().event(ctx, event); - msg.and_then(self.func) + self.as_mut().event(ctx, event) } else { self.next.event(i - 1, ctx, event) } @@ -201,19 +193,17 @@ where self.next.render_cloned(target); } - fn add( + fn add + MaybeTrace + Swipable + Clone>( self, elem: F, - func: fn(F::Msg) -> Option, ) -> Result where Self: Sized, { Ok(FlowComponent { elem: self.elem, - func: self.func, cloned: None, - next: self.next.add(elem, func)?, + next: self.next.add(elem)?, }) } } diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs index 40afe365a..66381204e 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_reset_device.rs @@ -6,7 +6,7 @@ use crate::{ ui::{ component::{ text::paragraphs::{Paragraph, Paragraphs}, - SwipeDirection, + ComponentExt, SwipeDirection, }, flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage}, }, @@ -86,7 +86,8 @@ impl ConfirmResetDevice { let paragraphs = Paragraphs::new(par_array); let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs)) .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None); + .with_footer(TR::instructions__swipe_up.into(), None) + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)); let content_menu = Frame::left_aligned( "".into(), @@ -95,29 +96,29 @@ impl ConfirmResetDevice { theme::ICON_CANCEL )]))), ) - .with_cancel_button(); + .with_cancel_button() + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); let content_confirm = Frame::left_aligned( TR::reset__title_create_wallet.into(), PromptScreen::new_hold_to_confirm(), ) - .with_footer(TR::instructions__hold_to_confirm.into(), None); + .with_footer(TR::instructions__hold_to_confirm.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + _ => Some(FlowMsg::Cancelled), + }); let store = flow_store() // Intro, - .add(content_intro, |msg| { - matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info) - })? + .add(content_intro)? // Context Menu, - .add(content_menu, |msg| match msg { - FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), - FrameMsg::Button(_) => Some(FlowMsg::Cancelled), - })? + .add(content_menu)? // Confirm prompt - .add(content_confirm, |msg| match msg { - FrameMsg::Content(()) => Some(FlowMsg::Confirmed), - _ => Some(FlowMsg::Cancelled), - })?; + .add(content_confirm)?; let res = SwipeFlow::new(ConfirmResetDevice::Intro, store)?; Ok(LayoutObj::new(res)?.into()) diff --git a/core/embed/rust/src/ui/model_mercury/flow/create_backup.rs b/core/embed/rust/src/ui/model_mercury/flow/create_backup.rs index e3fc8b8b3..43608ec2f 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/create_backup.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/create_backup.rs @@ -5,7 +5,7 @@ use crate::{ ui::{ component::{ text::paragraphs::{Paragraph, Paragraphs}, - SwipeDirection, + ComponentExt, SwipeDirection, }, flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage}, }, @@ -88,7 +88,10 @@ impl CreateBackup { let paragraphs = Paragraphs::new(par_array); let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs)) .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None); + .with_footer(TR::instructions__swipe_up.into(), None) + .map(|msg| { + matches!(msg, FrameMsg::Button(CancelInfoConfirmMsg::Info)).then_some(FlowMsg::Info) + }); let content_menu = Frame::left_aligned( "".into(), @@ -97,7 +100,12 @@ impl CreateBackup { theme::ICON_CANCEL )]))), ) - .with_cancel_button(); + .with_cancel_button() + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled), + FrameMsg::Button(_) => None, + }); let par_array_skip_intro: [Paragraph<'static>; 2] = [ Paragraph::new(&theme::TEXT_WARNING, TString::from_str("Not recommended!")), @@ -115,32 +123,28 @@ impl CreateBackup { .with_footer( TR::instructions__swipe_up.into(), Some(TR::words__continue_anyway.into()), - ); + ) + .map(|msg| match msg { + FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled), + _ => None, + }); let content_skip_confirm = Frame::left_aligned( TR::backup__title_skip.into(), PromptScreen::new_tap_to_cancel(), ) - .with_footer(TR::instructions__tap_to_confirm.into(), None); + .with_footer(TR::instructions__tap_to_confirm.into(), None) + .map(|msg| match msg { + FrameMsg::Content(()) => Some(FlowMsg::Confirmed), + FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled), + _ => None, + }); let store = flow_store() - .add(content_intro, |msg| { - matches!(msg, FrameMsg::Button(CancelInfoConfirmMsg::Info)).then_some(FlowMsg::Info) - })? - .add(content_menu, |msg| match msg { - FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), - FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled), - FrameMsg::Button(_) => None, - })? - .add(content_skip_intro, |msg| match msg { - FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled), - _ => None, - })? - .add(content_skip_confirm, |msg| match msg { - FrameMsg::Content(()) => Some(FlowMsg::Confirmed), - FrameMsg::Button(CancelInfoConfirmMsg::Cancelled) => Some(FlowMsg::Cancelled), - _ => None, - })?; + .add(content_intro)? + .add(content_menu)? + .add(content_skip_intro)? + .add(content_skip_confirm)?; let res = SwipeFlow::new(CreateBackup::Intro, store)?; Ok(LayoutObj::new(res)?.into()) } diff --git a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs index 116940075..4e2a65a71 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/get_address.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/get_address.rs @@ -4,7 +4,7 @@ use crate::{ component::{ image::BlendedImage, text::paragraphs::{Paragraph, Paragraphs}, - Qr, SwipeDirection, Timeout, + ButtonRequestExt, ComponentExt, Qr, SwipeDirection, Timeout, }, flow::{ base::Decision, flow_store, FlowMsg, FlowState, FlowStore, IgnoreSwipe, SwipeFlow, @@ -115,8 +115,8 @@ impl GetAddress { ))), ) .with_subtitle("address".into()) - .with_menu_button(), - |msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info), + .with_menu_button() + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)), )? .add( Frame::left_aligned( @@ -127,13 +127,13 @@ impl GetAddress { ("Cancel trans.", theme::ICON_CANCEL), ]))), ) - .with_cancel_button(), - |msg| match msg { + .with_cancel_button() + .map(|msg| match msg { FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => { Some(FlowMsg::Choice(i)) } FrameMsg::Button(_) => Some(FlowMsg::Cancelled), - }, + }), )? .add( Frame::left_aligned( @@ -143,8 +143,8 @@ impl GetAddress { true, )?), ) - .with_cancel_button(), - |msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled), + .with_cancel_button() + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled)), )? .add( Frame::left_aligned( @@ -154,8 +154,8 @@ impl GetAddress { StrBuffer::from("taproot xp"), ))), ) - .with_cancel_button(), - |msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled), + .with_cancel_button() + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled)), )? .add( Frame::left_aligned( @@ -165,8 +165,8 @@ impl GetAddress { StrBuffer::from("O rly?"), ))), ) - .with_cancel_button(), - |msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled), + .with_cancel_button() + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Cancelled)), )? .add( IconDialog::new( @@ -179,8 +179,8 @@ impl GetAddress { ), StrBuffer::from("Confirmed"), Timeout::new(100), - ), - |_| Some(FlowMsg::Confirmed), + ) + .map(|_| Some(FlowMsg::Confirmed)), )?; let res = SwipeFlow::new(GetAddress::Address, store)?; Ok(LayoutObj::new(res)?.into())