fix(core/ui): allow component to terminate flow on swipe

[skip_ci]
pull/3874/head
Martin Milata 1 month ago
parent cbcd9dd8bd
commit 9c14cae656

@ -65,15 +65,15 @@ pub trait ButtonRequestExt {
impl<T: Component> ButtonRequestExt for T {} impl<T: Component> ButtonRequestExt for T {}
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))] #[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
impl<T> crate::ui::flow::Swipable for OneButtonRequest<T> impl<T> crate::ui::flow::Swipable<T::Msg> for OneButtonRequest<T>
where where
T: Component + crate::ui::flow::Swipable, T: Component + crate::ui::flow::Swipable<T::Msg>,
{ {
fn swipe_start( fn swipe_start(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
direction: crate::ui::component::SwipeDirection, direction: crate::ui::component::SwipeDirection,
) -> bool { ) -> crate::ui::flow::SwipableResult<T::Msg> {
self.inner.swipe_start(ctx, direction) self.inner.swipe_start(ctx, direction)
} }

@ -52,12 +52,17 @@ where
} }
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))] #[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
impl<T, F> crate::ui::flow::Swipable for MsgMap<T, F> impl<T, F, U> crate::ui::flow::Swipable<U> for MsgMap<T, F>
where where
T: Component + crate::ui::flow::Swipable, T: Component + crate::ui::flow::Swipable<T::Msg>,
F: Fn(T::Msg) -> Option<U>,
{ {
fn swipe_start(&mut self, ctx: &mut EventCtx, direction: super::SwipeDirection) -> bool { fn swipe_start(
self.inner.swipe_start(ctx, direction) &mut self,
ctx: &mut EventCtx,
direction: super::SwipeDirection,
) -> crate::ui::flow::SwipableResult<U> {
self.inner.swipe_start(ctx, direction).map(&self.func)
} }
fn swipe_finished(&self) -> bool { fn swipe_finished(&self) -> bool {
@ -118,11 +123,15 @@ where
} }
#[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))] #[cfg(all(feature = "micropython", feature = "touch", feature = "new_rendering"))]
impl<T, F> crate::ui::flow::Swipable for PageMap<T, F> impl<T, F> crate::ui::flow::Swipable<T::Msg> for PageMap<T, F>
where where
T: Component + crate::ui::flow::Swipable, T: Component + crate::ui::flow::Swipable<T::Msg>,
{ {
fn swipe_start(&mut self, ctx: &mut EventCtx, direction: super::SwipeDirection) -> bool { fn swipe_start(
&mut self,
ctx: &mut EventCtx,
direction: super::SwipeDirection,
) -> crate::ui::flow::SwipableResult<T::Msg> {
self.inner.swipe_start(ctx, direction) self.inner.swipe_start(ctx, direction)
} }

@ -4,12 +4,18 @@ use num_traits::ToPrimitive;
/// Component must implement this trait in order to be part of swipe-based flow. /// Component must implement this trait in order to be part of swipe-based flow.
/// ///
/// Default implementation ignores every swipe. /// Default implementation ignores every swipe.
pub trait Swipable { pub trait Swipable<T> {
/// Attempt a swipe. Return false if component in its current state doesn't /// Attempt a swipe. Return `Ignored` if the component in its current state
/// accept a swipe in the given direction. Start a transition animation /// doesn't accept a swipe in that direction. Return `Animating` if
/// if true is returned. /// component accepted the swipe and started a transition animation. The
fn swipe_start(&mut self, _ctx: &mut EventCtx, _direction: SwipeDirection) -> bool { /// `Return(x)` variant indicates that the current flow should be terminated
false /// with the result `x`.
fn swipe_start(
&mut self,
_ctx: &mut EventCtx,
_direction: SwipeDirection,
) -> SwipableResult<T> {
SwipableResult::Ignored
} }
/// Return true when transition animation is finished. SwipeFlow needs to /// Return true when transition animation is finished. SwipeFlow needs to
@ -19,6 +25,28 @@ pub trait Swipable {
} }
} }
pub enum SwipableResult<T> {
Ignored,
Animating,
Return(T),
}
impl<T> SwipableResult<T> {
pub fn map<U>(self, func: impl FnOnce(T) -> Option<U>) -> SwipableResult<U> {
match self {
SwipableResult::Ignored => SwipableResult::Ignored,
SwipableResult::Animating => SwipableResult::Animating,
SwipableResult::Return(x) => {
if let Some(res) = func(x) {
SwipableResult::Return(res)
} else {
SwipableResult::Ignored
}
}
}
}
}
/// Component::Msg for component parts of a flow. Converting results of /// Component::Msg for component parts of a flow. Converting results of
/// different screens to a shared type makes things easier to work with. /// different screens to a shared type makes things easier to work with.
/// ///

@ -3,7 +3,7 @@ pub mod page;
mod store; mod store;
mod swipe; mod swipe;
pub use base::{FlowMsg, FlowState, Swipable}; pub use base::{FlowMsg, FlowState, Swipable, SwipableResult};
pub use page::{IgnoreSwipe, SwipePage}; pub use page::{IgnoreSwipe, SwipePage};
pub use store::{flow_store, FlowStore}; pub use store::{flow_store, FlowStore};
pub use swipe::SwipeFlow; pub use swipe::SwipeFlow;

@ -4,7 +4,7 @@ use crate::{
ui::{ ui::{
animation::Animation, animation::Animation,
component::{Component, Event, EventCtx, Paginate, SwipeDirection}, component::{Component, Event, EventCtx, Paginate, SwipeDirection},
flow::base::Swipable, flow::base::{Swipable, SwipableResult},
geometry::{Axis, Rect}, geometry::{Axis, Rect},
shape::Renderer, shape::Renderer,
util, util,
@ -117,17 +117,27 @@ impl<T: Component + Paginate + Clone> Component for SwipePage<T> {
} }
} }
impl<T: Component + Paginate + Clone> Swipable for SwipePage<T> { impl<T: Component + Paginate + Clone> Swipable<T::Msg> for SwipePage<T> {
fn swipe_start(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> bool { fn swipe_start(
&mut self,
ctx: &mut EventCtx,
direction: SwipeDirection,
) -> SwipableResult<T::Msg> {
match (self.axis, direction) { match (self.axis, direction) {
// Wrong direction // Wrong direction
(Axis::Horizontal, SwipeDirection::Up | SwipeDirection::Down) => return false, (Axis::Horizontal, SwipeDirection::Up | SwipeDirection::Down) => {
(Axis::Vertical, SwipeDirection::Left | SwipeDirection::Right) => return false, return SwipableResult::Ignored
}
(Axis::Vertical, SwipeDirection::Left | SwipeDirection::Right) => {
return SwipableResult::Ignored
}
// Begin // Begin
(_, SwipeDirection::Right | SwipeDirection::Down) if self.current == 0 => return false, (_, SwipeDirection::Right | SwipeDirection::Down) if self.current == 0 => {
return SwipableResult::Ignored
}
// End // End
(_, SwipeDirection::Left | SwipeDirection::Up) if self.current + 1 >= self.pages => { (_, SwipeDirection::Left | SwipeDirection::Up) if self.current + 1 >= self.pages => {
return false return SwipableResult::Ignored;
} }
_ => {} _ => {}
}; };
@ -138,7 +148,7 @@ impl<T: Component + Paginate + Clone> Swipable for SwipePage<T> {
if util::animation_disabled() { if util::animation_disabled() {
self.inner.change_page(self.current); self.inner.change_page(self.current);
ctx.request_paint(); ctx.request_paint();
return true; return SwipableResult::Animating;
} }
self.transition = Some(Transition { self.transition = Some(Transition {
cloned: unwrap!(Gc::new(self.inner.clone())), cloned: unwrap!(Gc::new(self.inner.clone())),
@ -148,7 +158,7 @@ impl<T: Component + Paginate + Clone> Swipable for SwipePage<T> {
self.inner.change_page(self.current); self.inner.change_page(self.current);
ctx.request_anim_frame(); ctx.request_anim_frame();
ctx.request_paint(); ctx.request_paint();
true SwipableResult::Animating
} }
fn swipe_finished(&self) -> bool { fn swipe_finished(&self) -> bool {
@ -195,7 +205,7 @@ impl<T: Component> Component for IgnoreSwipe<T> {
} }
} }
impl<T> Swipable for IgnoreSwipe<T> {} impl<T: Component> Swipable<T::Msg> for IgnoreSwipe<T> {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl<T> crate::trace::Trace for IgnoreSwipe<T> impl<T> crate::trace::Trace for IgnoreSwipe<T>

@ -29,10 +29,14 @@ pub trait FlowStore {
fn trace(&self, i: usize, t: &mut dyn crate::trace::Tracer); fn trace(&self, i: usize, t: &mut dyn crate::trace::Tracer);
/// Forward `Swipable` methods to i-th element. /// Forward `Swipable` methods to i-th element.
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T; fn map_swipable<T>(
&mut self,
i: usize,
func: impl FnOnce(&mut dyn Swipable<FlowMsg>) -> T,
) -> T;
/// Add a Component to the end of a `FlowStore`. /// Add a Component to the end of a `FlowStore`.
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>( fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>>(
self, self,
elem: E, elem: E,
) -> Result<impl FlowStore, error::Error> ) -> Result<impl FlowStore, error::Error>
@ -67,11 +71,15 @@ impl FlowStore for FlowEmpty {
panic!() panic!()
} }
fn map_swipable<T>(&mut self, _i: usize, _func: impl FnOnce(&mut dyn Swipable) -> T) -> T { fn map_swipable<T>(
&mut self,
_i: usize,
_func: impl FnOnce(&mut dyn Swipable<FlowMsg>) -> T,
) -> T {
panic!() panic!()
} }
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>( fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>>(
self, self,
elem: E, elem: E,
) -> Result<impl FlowStore, error::Error> ) -> Result<impl FlowStore, error::Error>
@ -107,7 +115,7 @@ impl<E: Component<Msg = FlowMsg>, P> FlowComponent<E, P> {
impl<E, P> FlowStore for FlowComponent<E, P> impl<E, P> FlowStore for FlowComponent<E, P>
where where
E: Component<Msg = FlowMsg> + MaybeTrace + Swipable, E: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>,
P: FlowStore, P: FlowStore,
{ {
fn place(&mut self, bounds: Rect) -> Rect { fn place(&mut self, bounds: Rect) -> Rect {
@ -141,7 +149,11 @@ where
} }
} }
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T { fn map_swipable<T>(
&mut self,
i: usize,
func: impl FnOnce(&mut dyn Swipable<FlowMsg>) -> T,
) -> T {
if i == 0 { if i == 0 {
func(self.as_mut()) func(self.as_mut())
} else { } else {
@ -149,7 +161,7 @@ where
} }
} }
fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable>( fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable<FlowMsg>>(
self, self,
elem: F, elem: F,
) -> Result<impl FlowStore, error::Error> ) -> Result<impl FlowStore, error::Error>

@ -4,7 +4,7 @@ use crate::{
ui::{ ui::{
animation::Animation, animation::Animation,
component::{Component, Event, EventCtx, Swipe, SwipeDirection}, component::{Component, Event, EventCtx, Swipe, SwipeDirection},
flow::{base::Decision, FlowMsg, FlowState, FlowStore}, flow::{base::Decision, FlowMsg, FlowState, FlowStore, SwipableResult},
geometry::Rect, geometry::Rect,
shape::Renderer, shape::Renderer,
util, util,
@ -135,13 +135,13 @@ impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
fn handle_swipe_child(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> Decision<Q> { fn handle_swipe_child(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> Decision<Q> {
let i = self.state.index(); let i = self.state.index();
if self match self
.store .store
.map_swipable(i, |s| s.swipe_start(ctx, direction)) .map_swipable(i, |s| s.swipe_start(ctx, direction))
{ {
Decision::Goto(self.state, direction) SwipableResult::Ignored => Decision::Nothing,
} else { SwipableResult::Animating => Decision::Goto(self.state, direction),
Decision::Nothing SwipableResult::Return(x) => Decision::Return(x),
} }
} }

@ -230,4 +230,4 @@ where
} }
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
impl<U> crate::ui::flow::Swipable for IconDialog<U> {} impl<U: Component> crate::ui::flow::Swipable<U::Msg> for IconDialog<U> {}

@ -252,16 +252,17 @@ where
} }
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
impl<T> crate::ui::flow::Swipable for Frame<T> impl<T> crate::ui::flow::Swipable<FrameMsg<T::Msg>> for Frame<T>
where where
T: Component + crate::ui::flow::Swipable, T: Component + crate::ui::flow::Swipable<T::Msg>,
{ {
fn swipe_start( fn swipe_start(
&mut self, &mut self,
ctx: &mut EventCtx, ctx: &mut EventCtx,
direction: crate::ui::component::SwipeDirection, direction: crate::ui::component::SwipeDirection,
) -> bool { ) -> crate::ui::flow::SwipableResult<FrameMsg<T::Msg>> {
self.update_content(ctx, |ctx, inner| inner.swipe_start(ctx, direction)) self.update_content(ctx, |ctx, inner| inner.swipe_start(ctx, direction))
.map(|x| Some(FrameMsg::Content(x)))
} }
fn swipe_finished(&self) -> bool { fn swipe_finished(&self) -> bool {

@ -134,7 +134,7 @@ impl Component for PromptScreen {
} }
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
impl crate::ui::flow::Swipable for PromptScreen {} impl crate::ui::flow::Swipable<()> for PromptScreen {}
#[cfg(feature = "ui_debug")] #[cfg(feature = "ui_debug")]
impl crate::trace::Trace for PromptScreen { impl crate::trace::Trace for PromptScreen {

@ -6,7 +6,7 @@ use crate::{
ui::{ ui::{
animation::Animation, animation::Animation,
component::{Component, Event, EventCtx, Never, SwipeDirection}, component::{Component, Event, EventCtx, Never, SwipeDirection},
flow::Swipable, flow::{Swipable, SwipableResult},
geometry::{Alignment, Alignment2D, Insets, Offset, Rect}, geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
model_mercury::component::Footer, model_mercury::component::Footer,
shape, shape,
@ -160,8 +160,12 @@ impl<'a> Component for ShareWords<'a> {
fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {} fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {}
} }
impl<'a> Swipable for ShareWords<'a> { impl<'a> Swipable<Never> for ShareWords<'a> {
fn swipe_start(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> bool { fn swipe_start(
&mut self,
ctx: &mut EventCtx,
direction: SwipeDirection,
) -> SwipableResult<Never> {
match direction { match direction {
SwipeDirection::Up if !self.is_final_page() => { SwipeDirection::Up if !self.is_final_page() => {
self.prev_index = self.page_index; self.prev_index = self.page_index;
@ -171,11 +175,11 @@ impl<'a> Swipable for ShareWords<'a> {
self.prev_index = self.page_index; self.prev_index = self.page_index;
self.page_index = self.page_index.saturating_sub(1); self.page_index = self.page_index.saturating_sub(1);
} }
_ => return false, _ => return SwipableResult::Ignored,
}; };
if util::animation_disabled() { if util::animation_disabled() {
ctx.request_paint(); ctx.request_paint();
return true; return SwipableResult::Animating;
} }
self.animation = Some(Animation::new( self.animation = Some(Animation::new(
0.0f32, 0.0f32,
@ -185,7 +189,7 @@ impl<'a> Swipable for ShareWords<'a> {
)); ));
ctx.request_anim_frame(); ctx.request_anim_frame();
ctx.request_paint(); ctx.request_paint();
true SwipableResult::Animating
} }
fn swipe_finished(&self) -> bool { fn swipe_finished(&self) -> bool {

@ -151,4 +151,4 @@ impl crate::trace::Trace for VerticalMenu {
} }
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
impl crate::ui::flow::Swipable for VerticalMenu {} impl crate::ui::flow::Swipable<VerticalMenuChoiceMsg> for VerticalMenu {}

@ -89,7 +89,7 @@ impl ConfirmBlobParams {
pub fn into_layout( pub fn into_layout(
self, self,
) -> Result<impl Component<Msg = FlowMsg> + Swipable + MaybeTrace, Error> { ) -> Result<impl Component<Msg = FlowMsg> + Swipable<FlowMsg> + MaybeTrace, Error> {
let paragraphs = ConfirmBlob { let paragraphs = ConfirmBlob {
description: self.description.unwrap_or("".into()), description: self.description.unwrap_or("".into()),
extra: self.extra.unwrap_or("".into()), extra: self.extra.unwrap_or("".into()),
@ -187,7 +187,7 @@ impl ShowInfoParams {
pub fn into_layout( pub fn into_layout(
self, self,
) -> Result<impl Component<Msg = FlowMsg> + Swipable + MaybeTrace, Error> { ) -> Result<impl Component<Msg = FlowMsg> + Swipable<FlowMsg> + MaybeTrace, Error> {
let mut paragraphs = ParagraphVecShort::new(); let mut paragraphs = ParagraphVecShort::new();
let mut first: bool = true; let mut first: bool = true;
for item in self.items { for item in self.items {

Loading…
Cancel
Save