1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-21 05:48:23 +00:00

refactor(core/rust): refactor SwipeFlow

* remove SwipeStore in favor of Vec<dyn FlowComponent>
* unify state and controllers
* implement tighter coupling between controller/states and pages of the
flow
This commit is contained in:
matejcik 2024-06-04 14:49:11 +02:00 committed by matejcik
parent 4c10a4f643
commit 3fcb0acaff
18 changed files with 754 additions and 1036 deletions

View File

@ -1,5 +1,4 @@
use crate::ui::component::{swipe_detect::SwipeConfig, SwipeDirection}; use crate::ui::component::{swipe_detect::SwipeConfig, SwipeDirection};
use num_traits::ToPrimitive;
pub trait Swipable { pub trait Swipable {
fn get_swipe_config(&self) -> SwipeConfig; fn get_swipe_config(&self) -> SwipeConfig;
@ -22,20 +21,20 @@ pub enum FlowMsg {
/// Composable event handler result. /// Composable event handler result.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum Decision<Q> { pub enum Decision {
/// Do nothing, continue with processing next handler. /// Do nothing, continue with processing next handler.
Nothing, Nothing,
/// Initiate transition to another state, end event processing. /// Initiate transition to another state, end event processing.
/// NOTE: it might make sense to include Option<ButtonRequest> here /// NOTE: it might make sense to include Option<ButtonRequest> here
Goto(Q, SwipeDirection), Transition(SwipeDirection),
/// Yield a message to the caller of the flow (i.e. micropython), end event /// Yield a message to the caller of the flow (i.e. micropython), end event
/// processing. /// processing.
Return(FlowMsg), Return(FlowMsg),
} }
impl<Q> Decision<Q> { impl Decision {
pub fn or_else(self, func: impl FnOnce() -> Self) -> Self { pub fn or_else(self, func: impl FnOnce() -> Self) -> Self {
match self { match self {
Decision::Nothing => func(), Decision::Nothing => func(),
@ -44,24 +43,71 @@ impl<Q> Decision<Q> {
} }
} }
/// State transition type.
///
/// Contains a new state (by convention it must be of the same concrete type as
/// the current one) and a Decision object that tells the flow what to do next.
pub type StateChange = (&'static dyn FlowState, Decision);
/// Encodes the flow logic as a set of states, and transitions between them /// Encodes the flow logic as a set of states, and transitions between them
/// triggered by events and swipes. /// triggered by events and swipes.
pub trait FlowState pub trait FlowState {
where
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
/// FlowStore has to have number of elements equal to number of states.
fn index(&self) -> usize {
unwrap!(self.to_usize())
}
/// What to do when user swipes on screen and current component doesn't /// What to do when user swipes on screen and current component doesn't
/// respond to swipe of that direction. /// respond to swipe of that direction.
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self>; ///
/// By convention, the type of the new state inside the state change must be
/// Self. This can't be enforced by the type system unfortunately, because
/// this trait must remain object-safe and so can't refer to Self.
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange;
/// What to do when the current component emits a message in response to an /// What to do when the current component emits a message in response to an
/// event. /// event.
fn handle_event(&self, msg: FlowMsg) -> Decision<Self>; ///
/// By convention, the type of the new state inside the state change must be
/// Self. This can't be enforced by the type system unfortunately, because
/// this trait must remain object-safe and so can't refer to Self.
fn handle_event(&'static self, msg: FlowMsg) -> StateChange;
/// Page index of the current state.
fn index(&'static self) -> usize;
} }
/// Helper trait for writing nicer flow logic.
pub trait DecisionBuilder: FlowState + Sized {
#[inline]
fn swipe(&'static self, direction: SwipeDirection) -> StateChange {
(self, Decision::Transition(direction))
}
#[inline]
fn swipe_left(&'static self) -> StateChange {
self.swipe(SwipeDirection::Left)
}
#[inline]
fn swipe_right(&'static self) -> StateChange {
self.swipe(SwipeDirection::Right)
}
#[inline]
fn swipe_up(&'static self) -> StateChange {
self.swipe(SwipeDirection::Up)
}
#[inline]
fn swipe_down(&'static self) -> StateChange {
self.swipe(SwipeDirection::Down)
}
#[inline]
fn do_nothing(&'static self) -> StateChange {
(self, Decision::Nothing)
}
#[inline]
fn return_msg(&'static self, msg: FlowMsg) -> StateChange {
(self, Decision::Return(msg))
}
}
impl<T: FlowState> DecisionBuilder for T {}

View File

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

View File

@ -1,190 +0,0 @@
use crate::{
error,
maybe_trace::MaybeTrace,
ui::{
component::{Component, Event, EventCtx},
flow::base::FlowMsg,
geometry::Rect,
shape::Renderer,
},
};
use crate::{
micropython::gc::Gc,
ui::{component::swipe_detect::SwipeConfig, flow::Swipable},
};
/// `FlowStore` is essentially `Vec<Gc<dyn Component + SimpleSwipable>>` except
/// that `trait Component` is not object-safe so it ends up being a kind of
/// recursively-defined tuple.
/// Implementors are something like the V in MVC.
pub trait FlowStore {
/// Call `Component::place` on all elements.
fn place(&mut self, bounds: Rect) -> Rect;
/// Call `Component::event` on i-th element.
fn event(&mut self, i: usize, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg>;
/// Call `Component::render` on i-th element.
fn render<'s>(&'s self, i: usize, target: &mut impl Renderer<'s>);
#[cfg(feature = "ui_debug")]
/// Call `Trace::trace` on i-th element.
fn trace(&self, i: usize, t: &mut dyn crate::trace::Tracer);
/// Forward `SimpleSwipable` methods to i-th element.
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T;
fn get_swipe_config(&self, i: usize) -> SwipeConfig;
fn get_internal_page_count(&mut self, i: usize) -> usize;
/// Add a Component to the end of a `FlowStore`.
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
self,
elem: E,
) -> Result<impl FlowStore, error::Error>
where
Self: Sized;
}
/// Create new empty flow store.
pub fn flow_store() -> impl FlowStore {
FlowEmpty {}
}
/// Terminating element of a recursive structure.
struct FlowEmpty;
// Methods that take an index panic because it's always out of bounds.
impl FlowStore for FlowEmpty {
fn place(&mut self, bounds: Rect) -> Rect {
bounds
}
fn event(&mut self, _i: usize, _ctx: &mut EventCtx, _event: Event) -> Option<FlowMsg> {
panic!()
}
fn render<'s>(&self, _i: usize, _target: &mut impl Renderer<'s>) {
panic!()
}
#[cfg(feature = "ui_debug")]
fn trace(&self, _i: usize, _t: &mut dyn crate::trace::Tracer) {
panic!()
}
fn map_swipable<T>(&mut self, _i: usize, _func: impl FnOnce(&mut dyn Swipable) -> T) -> T {
panic!()
}
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
self,
elem: E,
) -> Result<impl FlowStore, error::Error>
where
Self: Sized,
{
Ok(FlowComponent2 {
elem: Gc::new(elem)?,
next: Self,
})
}
fn get_swipe_config(&self, _i: usize) -> SwipeConfig {
SwipeConfig::new()
}
fn get_internal_page_count(&mut self, _i: usize) -> usize {
1
}
}
struct FlowComponent2<E: Component<Msg = FlowMsg>, P> {
/// Component allocated on micropython heap.
pub elem: Gc<E>,
/// Nested FlowStore.
pub next: P,
}
impl<E: Component<Msg = FlowMsg>, P> FlowComponent2<E, P> {
fn as_ref(&self) -> &E {
&self.elem
}
fn as_mut(&mut self) -> &mut E {
// SAFETY: micropython can only access this object through LayoutObj which wraps
// us in a RefCell which guarantees uniqueness
unsafe { Gc::as_mut(&mut self.elem) }
}
}
impl<E, P> FlowStore for FlowComponent2<E, P>
where
E: Component<Msg = FlowMsg> + MaybeTrace + Swipable,
P: FlowStore,
{
fn place(&mut self, bounds: Rect) -> Rect {
self.as_mut().place(bounds);
self.next.place(bounds);
bounds
}
fn event(&mut self, i: usize, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg> {
if i == 0 {
self.as_mut().event(ctx, event)
} else {
self.next.event(i - 1, ctx, event)
}
}
fn render<'s>(&'s self, i: usize, target: &mut impl Renderer<'s>) {
if i == 0 {
self.as_ref().render(target)
} else {
self.next.render(i - 1, target)
}
}
#[cfg(feature = "ui_debug")]
fn trace(&self, i: usize, t: &mut dyn crate::trace::Tracer) {
if i == 0 {
self.as_ref().trace(t)
} else {
self.next.trace(i - 1, t)
}
}
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T {
if i == 0 {
func(self.as_mut())
} else {
self.next.map_swipable(i - 1, func)
}
}
fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
self,
elem: F,
) -> Result<impl FlowStore, error::Error>
where
Self: Sized,
{
Ok(FlowComponent2 {
elem: self.elem,
next: self.next.add(elem)?,
})
}
fn get_swipe_config(&self, i: usize) -> SwipeConfig {
if i == 0 {
self.as_ref().get_swipe_config()
} else {
self.next.get_swipe_config(i - 1)
}
}
fn get_internal_page_count(&mut self, i: usize) -> usize {
self.map_swipable(i, |swipable| swipable.get_internal_page_count())
}
}

View File

@ -1,29 +1,103 @@
use crate::{ use crate::{
error, error::{self, Error},
maybe_trace::MaybeTrace,
micropython::{
gc::{self, GcBox},
obj::Obj,
},
ui::{ ui::{
component::{ component::{
base::AttachType, swipe_detect::SwipeSettings, Component, Event, EventCtx, SwipeDetect, base::AttachType, swipe_detect::SwipeSettings, Component, Event, EventCtx, SwipeDetect,
SwipeDetectMsg, SwipeDirection, SwipeDetectMsg, SwipeDirection,
}, },
display::Color,
event::{SwipeEvent, TouchEvent}, event::{SwipeEvent, TouchEvent},
flow::{base::Decision, FlowMsg, FlowState, FlowStore}, flow::{base::Decision, FlowMsg, FlowState},
geometry::Rect, geometry::Rect,
shape::Renderer, layout::obj::ObjComponent,
shape::{render_on_display, ConcreteRenderer, Renderer, ScopedRenderer},
util::animation_disabled, util::animation_disabled,
}, },
}; };
/// Given a state enum and a corresponding FlowStore, create a Component that use super::{base::StateChange, Swipable};
/// implements a swipe navigation between the states with animated transitions.
use heapless::Vec;
/// Component-like proto-object-safe trait.
///
/// This copies the Component interface, but it is parametrized by a concrete
/// Renderer type, making it object-safe.
pub trait FlowComponentTrait<'s, R: Renderer<'s>>: Swipable {
fn place(&mut self, bounds: Rect) -> Rect;
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg>;
fn render(&'s self, target: &mut R);
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn crate::trace::Tracer);
}
/// FlowComponentTrait implementation for Components.
///
/// Components implementing FlowComponentTrait must:
/// * also implement Swipable, required by FlowComponentTrait,
/// * use FlowMsg as their Msg type,
/// * implement MaybeTrace to be able to conform to ObjComponent.
impl<'s, R, C> FlowComponentTrait<'s, R> for C
where
C: Component<Msg = FlowMsg> + MaybeTrace + Swipable,
R: Renderer<'s>,
{
fn place(&mut self, bounds: Rect) -> Rect {
<Self as Component>::place(self, bounds)
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg> {
<Self as Component>::event(self, ctx, event)
}
fn render(&'s self, target: &mut R) {
<Self as Component>::render(self, target)
}
#[cfg(feature = "ui_debug")]
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
<Self as crate::trace::Trace>::trace(self, t)
}
}
/// Shortcut type for the concrete renderer passed into `render()` method.
type RendererImpl<'a, 'alloc, 'env> = ScopedRenderer<'alloc, 'env, ConcreteRenderer<'a, 'alloc>>;
/// Fully object-safe component-like trait for flow components.
///
/// This trait has no generic parameters:
/// * it is instantiated with a concrete Renderer type, and
/// * it requires the `FlowComponentTrait` trait to be implemented for _any_
/// lifetimes.
pub trait FlowComponentDynTrait:
for<'a, 'alloc, 'env> FlowComponentTrait<'alloc, RendererImpl<'a, 'alloc, 'env>>
{
}
impl<T> FlowComponentDynTrait for T where
T: for<'a, 'alloc, 'env> FlowComponentTrait<'alloc, RendererImpl<'a, 'alloc, 'env>>
{
}
/// Swipe flow consisting of multiple screens.
///
/// Implements swipe navigation between the states with animated transitions,
/// based on state transitions provided by the FlowState type.
/// ///
/// If a swipe is detected: /// If a swipe is detected:
/// - currently active component is asked to handle the event, /// - currently active component is asked to handle the event,
/// - if it can't then FlowState::handle_swipe is consulted. /// - if it can't then FlowState::handle_swipe is consulted.
pub struct SwipeFlow<Q, S> { pub struct SwipeFlow {
/// Current state. /// Current state of the flow.
state: Q, state: &'static dyn FlowState,
/// FlowStore with all screens/components. /// Store of all screens which are part of the flow.
store: S, store: Vec<GcBox<dyn FlowComponentDynTrait>, 10>,
/// Swipe detector. /// Swipe detector.
swipe: SwipeDetect, swipe: SwipeDetect,
/// Swipe allowed /// Swipe allowed
@ -34,33 +108,53 @@ pub struct SwipeFlow<Q, S> {
internal_pages: u16, internal_pages: u16,
/// If triggering swipe by event, make this decision instead of default /// If triggering swipe by event, make this decision instead of default
/// after the swipe. /// after the swipe.
decision_override: Option<Decision<Q>>, decision_override: Option<StateChange>,
} }
impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> { impl SwipeFlow {
pub fn new(init: Q, store: S) -> Result<Self, error::Error> { pub fn new(initial_state: &'static dyn FlowState) -> Result<Self, error::Error> {
Ok(Self { Ok(Self {
state: init, state: initial_state,
swipe: SwipeDetect::new(), swipe: SwipeDetect::new(),
store, store: Vec::new(),
allow_swipe: true, allow_swipe: true,
internal_state: 0, internal_state: 0,
internal_pages: 1, internal_pages: 1,
decision_override: None, decision_override: None,
}) })
} }
fn goto(&mut self, ctx: &mut EventCtx, direction: SwipeDirection, state: Q) {
self.state = state; /// Add a page to the flow.
///
/// Pages must be inserted in the order of the flow state index.
pub fn with_page(
mut self,
state: &'static dyn FlowState,
page: impl FlowComponentDynTrait + 'static,
) -> Result<Self, error::Error> {
debug_assert!(self.store.len() == state.index());
let alloc = GcBox::new(page)?;
let page = gc::coerce!(FlowComponentDynTrait, alloc);
unwrap!(self.store.push(page));
Ok(self)
}
fn current_page(&self) -> &GcBox<dyn FlowComponentDynTrait> {
&self.store[self.state.index()]
}
fn current_page_mut(&mut self) -> &mut GcBox<dyn FlowComponentDynTrait> {
&mut self.store[self.state.index()]
}
fn goto(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) {
self.swipe = SwipeDetect::new(); self.swipe = SwipeDetect::new();
self.allow_swipe = true; self.allow_swipe = true;
self.store.event( self.current_page_mut()
state.index(), .event(ctx, Event::Attach(AttachType::Swipe(direction)));
ctx,
Event::Attach(AttachType::Swipe(direction)),
);
self.internal_pages = self.store.get_internal_page_count(state.index()) as u16; self.internal_pages = self.current_page_mut().get_internal_page_count() as u16;
match direction { match direction {
SwipeDirection::Up => { SwipeDirection::Up => {
@ -75,46 +169,43 @@ impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
ctx.request_paint(); ctx.request_paint();
} }
fn render_state<'s>(&'s self, state: Q, target: &mut impl Renderer<'s>) { fn render_state<'s>(&'s self, state: usize, target: &mut RendererImpl<'_, 's, '_>) {
self.store.render(state.index(), target) self.store[state].render(target);
} }
fn handle_swipe_child( fn handle_swipe_child(
&mut self, &mut self,
_ctx: &mut EventCtx, _ctx: &mut EventCtx,
direction: SwipeDirection, direction: SwipeDirection,
) -> Decision<Q> { ) -> StateChange {
self.state.handle_swipe(direction) self.state.handle_swipe(direction)
} }
fn handle_event_child(&mut self, ctx: &mut EventCtx, event: Event) -> Decision<Q> { fn handle_event_child(&mut self, ctx: &mut EventCtx, event: Event) -> StateChange {
let msg = self.store.event(self.state.index(), ctx, event); let msg = self.current_page_mut().event(ctx, event);
if let Some(msg) = msg { if let Some(msg) = msg {
self.state.handle_event(msg) self.state.handle_event(msg)
} else { } else {
Decision::Nothing (self.state, Decision::Nothing)
} }
} }
}
impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> { fn state_unchanged(&self) -> StateChange {
type Msg = FlowMsg; (self.state, Decision::Nothing)
fn place(&mut self, bounds: Rect) -> Rect {
self.store.place(bounds)
} }
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg> {
let mut decision: Decision<Q> = Decision::Nothing; let mut state_change = self.state_unchanged();
let mut return_transition: AttachType = AttachType::Initial; let mut return_transition: AttachType = AttachType::Initial;
let mut attach = false; let mut attach = false;
let e = if self.allow_swipe { let e = if self.allow_swipe {
let mut config = self.store.get_swipe_config(self.state.index()); let page = self.current_page();
let mut config = page.get_swipe_config();
self.internal_pages = self.store.get_internal_page_count(self.state.index()) as u16; self.internal_pages = page.get_internal_page_count() as u16;
// add additional swipe directions if there are more internal pages // add additional swipe directions if there are more internal pages
// todo can we get internal settings from config somehow? // todo can we get internal settings from config somehow?
@ -135,9 +226,9 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
match self.swipe.event(ctx, event, config) { match self.swipe.event(ctx, event, config) {
Some(SwipeDetectMsg::Trigger(dir)) => { Some(SwipeDetectMsg::Trigger(dir)) => {
if let Some(override_decision) = self.decision_override.take() { if let Some(override_decision) = self.decision_override.take() {
decision = override_decision; state_change = override_decision;
} else { } else {
decision = self.handle_swipe_child(ctx, dir); state_change = self.handle_swipe_child(ctx, dir);
} }
return_transition = AttachType::Swipe(dir); return_transition = AttachType::Swipe(dir);
@ -148,11 +239,11 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
let current_state = self.internal_state; let current_state = self.internal_state;
if dir == SwipeDirection::Left && current_state < states_num - 1 { if dir == SwipeDirection::Left && current_state < states_num - 1 {
self.internal_state += 1; self.internal_state += 1;
decision = Decision::Nothing; state_change = self.state_unchanged();
attach = true; attach = true;
} else if dir == SwipeDirection::Right && current_state > 0 { } else if dir == SwipeDirection::Right && current_state > 0 {
self.internal_state -= 1; self.internal_state -= 1;
decision = Decision::Nothing; state_change = self.state_unchanged();
attach = true; attach = true;
} }
} }
@ -160,77 +251,75 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
let current_state = self.internal_state; let current_state = self.internal_state;
if dir == SwipeDirection::Up && current_state < states_num - 1 { if dir == SwipeDirection::Up && current_state < states_num - 1 {
self.internal_state += 1; self.internal_state += 1;
decision = Decision::Nothing; state_change = self.state_unchanged();
attach = true; attach = true;
} else if dir == SwipeDirection::Down && current_state > 0 { } else if dir == SwipeDirection::Down && current_state > 0 {
self.internal_state -= 1; self.internal_state -= 1;
decision = Decision::Nothing; state_change = self.state_unchanged();
attach = true; attach = true;
} }
} }
} }
Some(Event::Swipe(SwipeEvent::End(dir))) Event::Swipe(SwipeEvent::End(dir))
} }
Some(SwipeDetectMsg::Move(dir, progress)) => { Some(SwipeDetectMsg::Move(dir, progress)) => {
Some(Event::Swipe(SwipeEvent::Move(dir, progress as i16))) Event::Swipe(SwipeEvent::Move(dir, progress as i16))
} }
Some(SwipeDetectMsg::Start(_)) => Some(Event::Touch(TouchEvent::TouchAbort)), Some(SwipeDetectMsg::Start(_)) => Event::Touch(TouchEvent::TouchAbort),
_ => Some(event), _ => event,
} }
} else { } else {
Some(event) event
}; };
if let Some(e) = e { match state_change {
match decision { (_, Decision::Nothing) => {
Decision::Nothing => { state_change = self.handle_event_child(ctx, e);
decision = self.handle_event_child(ctx, e);
// when doing internal transition, pass attach event to the child after sending // when doing internal transition, pass attach event to the child after sending
// swipe end. // swipe end.
if attach { if attach {
if let Event::Swipe(SwipeEvent::End(dir)) = e { if let Event::Swipe(SwipeEvent::End(dir)) = e {
self.store.event( self.current_page_mut()
self.state.index(), .event(ctx, Event::Attach(AttachType::Swipe(dir)));
ctx, }
Event::Attach(AttachType::Swipe(dir)), }
);
if ctx.disable_swipe_requested() {
self.swipe.reset();
self.allow_swipe = false;
}
if ctx.enable_swipe_requested() {
self.swipe.reset();
self.allow_swipe = true;
};
let config = self.current_page().get_swipe_config();
if let (_, Decision::Transition(direction)) = state_change {
if config.is_allowed(direction) {
if !animation_disabled() {
self.swipe.trigger(ctx, direction, config);
self.decision_override = Some(state_change);
state_change = self.state_unchanged();
} }
}
if ctx.disable_swipe_requested() {
self.swipe.reset();
self.allow_swipe = false;
}
if ctx.enable_swipe_requested() {
self.swipe.reset();
self.allow_swipe = true; self.allow_swipe = true;
};
let config = self.store.get_swipe_config(self.state.index());
if let Decision::Goto(_, direction) = decision {
if config.is_allowed(direction) {
if !animation_disabled() {
self.swipe.trigger(ctx, direction, config);
self.decision_override = Some(decision);
decision = Decision::Nothing;
}
self.allow_swipe = true;
}
} }
} }
_ => { }
//ignore message, we are already transitioning _ => {
self.store.event(self.state.index(), ctx, event); //ignore message, we are already transitioning
} self.current_page_mut().event(ctx, event);
} }
} }
let (new_state, decision) = state_change;
self.state = new_state;
match decision { match decision {
Decision::Goto(next_state, direction) => { Decision::Transition(direction) => {
self.goto(ctx, direction, next_state); self.state = new_state;
self.goto(ctx, direction);
None None
} }
Decision::Return(msg) => { Decision::Return(msg) => {
@ -242,34 +331,49 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
_ => None, _ => None,
} }
} }
fn paint(&mut self) {}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.render_state(self.state, target);
}
}
#[cfg(feature = "ui_debug")]
impl<Q: FlowState, S: FlowStore> crate::trace::Trace for SwipeFlow<Q, S> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
self.store.trace(self.state.index(), t)
}
} }
/// ObjComponent implementation for SwipeFlow.
///
/// Instead of using the generic `impl ObjComponent for ComponentMsgObj`, we
/// provide our own short-circuit implementation for `SwipeFlow`. This way we
/// can completely avoid implementing `Component`. That also allows us to pass
/// around concrete Renderers instead of having to conform to `Component`'s
/// not-object-safe interface.
///
/// This implementation relies on the fact that swipe components always return
/// `FlowMsg` as their `Component::Msg` (provided by `impl FlowComponentTrait`
/// earlier in this file).
#[cfg(feature = "micropython")] #[cfg(feature = "micropython")]
impl<Q: FlowState, S: FlowStore> crate::ui::layout::obj::ComponentMsgObj for SwipeFlow<Q, S> { impl ObjComponent for SwipeFlow {
fn msg_try_into_obj( fn obj_place(&mut self, bounds: Rect) -> Rect {
&self, for elem in self.store.iter_mut() {
msg: Self::Msg, elem.place(bounds);
) -> Result<crate::micropython::obj::Obj, error::Error> { }
match msg { bounds
FlowMsg::Confirmed => Ok(crate::ui::layout::result::CONFIRMED.as_obj()), }
FlowMsg::Cancelled => Ok(crate::ui::layout::result::CANCELLED.as_obj()),
FlowMsg::Info => Ok(crate::ui::layout::result::INFO.as_obj()), fn obj_event(&mut self, ctx: &mut EventCtx, event: Event) -> Result<Obj, Error> {
FlowMsg::Choice(i) => { match self.event(ctx, event) {
None => Ok(Obj::const_none()),
Some(FlowMsg::Confirmed) => Ok(crate::ui::layout::result::CONFIRMED.as_obj()),
Some(FlowMsg::Cancelled) => Ok(crate::ui::layout::result::CANCELLED.as_obj()),
Some(FlowMsg::Info) => Ok(crate::ui::layout::result::INFO.as_obj()),
Some(FlowMsg::Choice(i)) => {
Ok((crate::ui::layout::result::CONFIRMED.as_obj(), i.try_into()?).try_into()?) Ok((crate::ui::layout::result::CONFIRMED.as_obj(), i.try_into()?).try_into()?)
} }
} }
} }
fn obj_paint(&mut self) {
render_on_display(None, Some(Color::black()), |target| {
self.render_state(self.state.index(), target);
});
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for SwipeFlow {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
self.current_page().trace(t)
}
} }

View File

@ -319,8 +319,7 @@ impl LayoutObjInner {
impl LayoutObj { impl LayoutObj {
/// Create a new `LayoutObj`, wrapping a root component. /// Create a new `LayoutObj`, wrapping a root component.
#[inline(never)] pub fn new(root: impl ObjComponent + 'static) -> Result<Gc<Self>, Error> {
pub fn new(root: impl ComponentMsgObj + MaybeTrace + 'static) -> Result<Gc<Self>, Error> {
// SAFETY: This is a Python object and hase a base as first element // SAFETY: This is a Python object and hase a base as first element
unsafe { unsafe {
Gc::new_with_custom_finaliser(Self { Gc::new_with_custom_finaliser(Self {

View File

@ -1,123 +1,101 @@
use crate::{ use crate::{
error, error,
maybe_trace::MaybeTrace, maybe_trace::MaybeTrace,
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{text::paragraphs::Paragraph, ComponentExt, SwipeDirection}, component::{
flow::{base::Decision, FlowMsg, FlowState, FlowStore}, swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, VecExt},
Component, ComponentExt, Paginate, SwipeDirection,
},
flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow, SwipePage,
},
layout::obj::LayoutObj,
}, },
}; };
use super::super::{ use super::super::{
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg}, component::{Frame, FrameMsg, PromptScreen, SwipeContent, VerticalMenu, VerticalMenuChoiceMsg},
theme, theme,
}; };
// TODO: merge with code from https://github.com/trezor/trezor-firmware/pull/3805 // TODO: merge with code from https://github.com/trezor/trezor-firmware/pull/3805
// when ready // when ready
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmAction { pub enum ConfirmAction {
Intro, Intro,
Menu, Menu,
Confirm, Confirm,
} }
impl FlowState for ConfirmAction {
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) {
(Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
(Self::Menu, SwipeDirection::Right) => Self::Intro.swipe(direction),
(Self::Intro, SwipeDirection::Up) => Self::Confirm.swipe(direction),
(Self::Confirm, SwipeDirection::Down) => Self::Intro.swipe(direction),
(Self::Confirm, SwipeDirection::Left) => Self::Menu.swipe(direction),
_ => self.do_nothing(),
}
}
fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) {
(Self::Intro, FlowMsg::Info) => Self::Menu.swipe_left(),
(Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
(Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Info),
(Self::Confirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
(Self::Confirm, FlowMsg::Info) => Self::Menu.swipe_left(),
_ => self.do_nothing(),
}
}
}
/// ConfirmAction flow without a separate "Tap to confirm" or "Hold to confirm" /// ConfirmAction flow without a separate "Tap to confirm" or "Hold to confirm"
/// screen. Swiping up directly from the intro screen confirms action. /// screen. Swiping up directly from the intro screen confirms action.
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmActionSimple { pub enum ConfirmActionSimple {
Intro, Intro,
Menu, Menu,
} }
impl FlowState for ConfirmAction {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> {
match (self, direction) {
(ConfirmAction::Intro, SwipeDirection::Left) => {
Decision::Goto(ConfirmAction::Menu, direction)
}
(ConfirmAction::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmAction::Intro, direction)
}
(ConfirmAction::Intro, SwipeDirection::Up) => {
Decision::Goto(ConfirmAction::Confirm, direction)
}
(ConfirmAction::Confirm, SwipeDirection::Down) => {
Decision::Goto(ConfirmAction::Intro, direction)
}
(ConfirmAction::Confirm, SwipeDirection::Left) => {
Decision::Goto(ConfirmAction::Menu, direction)
}
_ => Decision::Nothing,
}
}
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> {
match (self, msg) {
(ConfirmAction::Intro, FlowMsg::Info) => {
Decision::Goto(ConfirmAction::Menu, SwipeDirection::Left)
}
(ConfirmAction::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmAction::Intro, SwipeDirection::Right)
}
(ConfirmAction::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
(ConfirmAction::Menu, FlowMsg::Choice(1)) => Decision::Return(FlowMsg::Info),
(ConfirmAction::Confirm, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed),
(ConfirmAction::Confirm, FlowMsg::Info) => {
Decision::Goto(ConfirmAction::Menu, SwipeDirection::Left)
}
_ => Decision::Nothing,
}
}
}
impl FlowState for ConfirmActionSimple { impl FlowState for ConfirmActionSimple {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ConfirmActionSimple::Intro, SwipeDirection::Left) => { (Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(ConfirmActionSimple::Menu, direction) (Self::Menu, SwipeDirection::Right) => Self::Intro.swipe(direction),
} (Self::Intro, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
(ConfirmActionSimple::Menu, SwipeDirection::Right) => { _ => self.do_nothing(),
Decision::Goto(ConfirmActionSimple::Intro, direction)
}
(ConfirmActionSimple::Intro, SwipeDirection::Up) => {
Decision::Return(FlowMsg::Confirmed)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(ConfirmActionSimple::Intro, FlowMsg::Info) => { (Self::Intro, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(ConfirmActionSimple::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
} (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(ConfirmActionSimple::Menu, FlowMsg::Cancelled) => { (Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Info),
Decision::Goto(ConfirmActionSimple::Intro, SwipeDirection::Right) _ => self.do_nothing(),
}
(ConfirmActionSimple::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
(ConfirmActionSimple::Menu, FlowMsg::Choice(1)) => Decision::Return(FlowMsg::Info),
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, qstr::Qstr, util},
ui::{
component::{
swipe_detect::SwipeSettings,
text::paragraphs::{ParagraphSource, ParagraphVecShort, VecExt},
Component, Paginate,
},
flow::{flow_store, SwipeFlow, SwipePage},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_confirm_action(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_confirm_action_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, new_confirm_action_obj) }
@ -248,15 +226,15 @@ pub fn new_confirm_action_uni<T: Component + MaybeTrace + 'static>(
FrameMsg::Button(_) => Some(FlowMsg::Info), FrameMsg::Button(_) => Some(FlowMsg::Info),
}); });
let store = flow_store() let res = SwipeFlow::new(&ConfirmAction::Intro)?
.add(content_intro)? .with_page(&ConfirmAction::Intro, content_intro)?
.add(content_menu)? .with_page(&ConfirmAction::Menu, content_menu)?
.add(content_confirm)?; .with_page(&ConfirmAction::Confirm, content_confirm)?;
let res = SwipeFlow::new(ConfirmAction::Intro, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} else { } else {
let store = flow_store().add(content_intro)?.add(content_menu)?; let res = SwipeFlow::new(&ConfirmActionSimple::Intro)?
let res = SwipeFlow::new(ConfirmActionSimple::Intro, store)?; .with_page(&ConfirmActionSimple::Intro, content_intro)?
.with_page(&ConfirmActionSimple::Menu, content_menu)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,25 +1,31 @@
use crate::{ use crate::{
error, error,
micropython::qstr::Qstr, micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
button_request::ButtonRequest, button_request::ButtonRequest,
component::{ButtonRequestExt, ComponentExt, SwipeDirection}, component::{swipe_detect::SwipeSettings, ButtonRequestExt, ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
use super::super::{ use super::{
component::{ super::{
AddressDetails, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg, component::{
AddressDetails, Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg,
},
theme,
}, },
theme, util::ConfirmBlobParams,
}; };
use super::util::ConfirmBlobParams; #[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmOutput { pub enum ConfirmOutput {
Address, Address,
Amount, Amount,
@ -30,58 +36,39 @@ pub enum ConfirmOutput {
} }
impl FlowState for ConfirmOutput { impl FlowState for ConfirmOutput {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ConfirmOutput::Address | ConfirmOutput::Amount, SwipeDirection::Left) => { (Self::Address | Self::Amount, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(ConfirmOutput::Menu, direction) (Self::Address, SwipeDirection::Up) => Self::Amount.swipe(direction),
(Self::Amount, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
(Self::Amount, SwipeDirection::Down) => Self::Address.swipe(direction),
(Self::Menu, SwipeDirection::Right) => Self::Address.swipe(direction),
(Self::Menu, SwipeDirection::Left) => Self::AccountInfo.swipe(direction),
(Self::AccountInfo | Self::CancelTap, SwipeDirection::Right) => {
Self::Menu.swipe(direction)
} }
(ConfirmOutput::Address, SwipeDirection::Up) => { _ => self.do_nothing(),
Decision::Goto(ConfirmOutput::Amount, direction)
}
(ConfirmOutput::Amount, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
(ConfirmOutput::Amount, SwipeDirection::Down) => {
Decision::Goto(ConfirmOutput::Address, direction)
}
(ConfirmOutput::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmOutput::Address, direction)
}
(ConfirmOutput::Menu, SwipeDirection::Left) => {
Decision::Goto(ConfirmOutput::AccountInfo, direction)
}
(ConfirmOutput::AccountInfo | ConfirmOutput::CancelTap, SwipeDirection::Right) => {
Decision::Goto(ConfirmOutput::Menu, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(_, FlowMsg::Info) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Left), (_, FlowMsg::Info) => Self::Menu.swipe_left(),
(ConfirmOutput::Menu, FlowMsg::Choice(0)) => { (Self::Menu, FlowMsg::Choice(0)) => Self::AccountInfo.swipe_left(),
Decision::Goto(ConfirmOutput::AccountInfo, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(1)) => Self::CancelTap.swipe_left(),
} (Self::Menu, FlowMsg::Cancelled) => Self::Address.swipe_right(),
(ConfirmOutput::Menu, FlowMsg::Choice(1)) => { (Self::CancelTap, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
Decision::Goto(ConfirmOutput::CancelTap, SwipeDirection::Left) (_, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
} _ => self.do_nothing(),
(ConfirmOutput::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmOutput::Address, SwipeDirection::Right)
}
(ConfirmOutput::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled),
(_, FlowMsg::Cancelled) => Decision::Goto(ConfirmOutput::Menu, SwipeDirection::Right),
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmOutput::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmOutput::new_obj) }
@ -156,13 +143,12 @@ impl ConfirmOutput {
FrameMsg::Button(_) => Some(FlowMsg::Cancelled), FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
}); });
let store = flow_store() let res = SwipeFlow::new(&ConfirmOutput::Address)?
.add(content_address)? .with_page(&ConfirmOutput::Address, content_address)?
.add(content_amount)? .with_page(&ConfirmOutput::Amount, content_amount)?
.add(content_menu)? .with_page(&ConfirmOutput::Menu, content_menu)?
.add(content_account)? .with_page(&ConfirmOutput::AccountInfo, content_account)?
.add(content_cancel_tap)?; .with_page(&ConfirmOutput::CancelTap, content_cancel_tap)?;
let res = SwipeFlow::new(ConfirmOutput::Address, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -10,7 +10,10 @@ use crate::{
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ButtonRequestExt, ComponentExt, SwipeDirection, ButtonRequestExt, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj, layout::obj::LayoutObj,
model_mercury::component::{PromptScreen, SwipeContent}, model_mercury::component::{PromptScreen, SwipeContent},
}, },
@ -21,7 +24,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmResetCreate { pub enum ConfirmResetCreate {
Intro, Intro,
Menu, Menu,
@ -29,43 +32,30 @@ pub enum ConfirmResetCreate {
} }
impl FlowState for ConfirmResetCreate { impl FlowState for ConfirmResetCreate {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ConfirmResetCreate::Intro, SwipeDirection::Left) => { (Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(ConfirmResetCreate::Menu, direction) (Self::Intro, SwipeDirection::Up) => Self::Confirm.swipe(direction),
} (Self::Menu, SwipeDirection::Right) => Self::Intro.swipe(direction),
(ConfirmResetCreate::Intro, SwipeDirection::Up) => { (Self::Confirm, SwipeDirection::Down) => Self::Intro.swipe(direction),
Decision::Goto(ConfirmResetCreate::Confirm, direction) (Self::Confirm, SwipeDirection::Left) => Self::Menu.swipe(direction),
} _ => self.do_nothing(),
(ConfirmResetCreate::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmResetCreate::Intro, direction)
}
(ConfirmResetCreate::Confirm, SwipeDirection::Down) => {
Decision::Goto(ConfirmResetCreate::Intro, direction)
}
(ConfirmResetCreate::Confirm, SwipeDirection::Left) => {
Decision::Goto(ConfirmResetCreate::Menu, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(ConfirmResetCreate::Intro, FlowMsg::Info) => { (Self::Intro, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(ConfirmResetCreate::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
} (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(ConfirmResetCreate::Menu, FlowMsg::Cancelled) => { (Self::Confirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
Decision::Goto(ConfirmResetCreate::Intro, SwipeDirection::Right) (Self::Confirm, FlowMsg::Info) => Self::Menu.swipe_left(),
} _ => self.do_nothing(),
(ConfirmResetCreate::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
(ConfirmResetCreate::Confirm, FlowMsg::Confirmed) => {
Decision::Return(FlowMsg::Confirmed)
}
(ConfirmResetCreate::Confirm, FlowMsg::Info) => {
Decision::Goto(ConfirmResetCreate::Menu, SwipeDirection::Left)
}
_ => Decision::Nothing,
} }
} }
} }
@ -124,12 +114,10 @@ impl ConfirmResetCreate {
}) })
.one_button_request(ButtonRequestCode::ResetDevice.with_type("confirm_setup_device")); .one_button_request(ButtonRequestCode::ResetDevice.with_type("confirm_setup_device"));
let store = flow_store() let res = SwipeFlow::new(&ConfirmResetCreate::Intro)?
.add(content_intro)? .with_page(&ConfirmResetCreate::Intro, content_intro)?
.add(content_menu)? .with_page(&ConfirmResetCreate::Menu, content_menu)?
.add(content_confirm)?; .with_page(&ConfirmResetCreate::Confirm, content_confirm)?;
let res = SwipeFlow::new(ConfirmResetCreate::Intro, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,13 +1,20 @@
use crate::{ use crate::{
error, error,
micropython::{map::Map, obj::Obj, util},
translations::TR, translations::TR,
ui::{ ui::{
button_request::ButtonRequestCode, button_request::ButtonRequestCode,
component::{ component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ButtonRequestExt, ComponentExt, SwipeDirection, ButtonRequestExt, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
@ -16,51 +23,37 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum ConfirmResetRecover { pub enum ConfirmResetRecover {
Intro, Intro,
Menu, Menu,
} }
impl FlowState for ConfirmResetRecover { impl FlowState for ConfirmResetRecover {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ConfirmResetRecover::Intro, SwipeDirection::Left) => { (Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(ConfirmResetRecover::Menu, direction) (Self::Menu, SwipeDirection::Right) => Self::Intro.swipe(direction),
} (Self::Intro, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
(ConfirmResetRecover::Menu, SwipeDirection::Right) => { _ => self.do_nothing(),
Decision::Goto(ConfirmResetRecover::Intro, direction)
}
(ConfirmResetRecover::Intro, SwipeDirection::Up) => {
Decision::Return(FlowMsg::Confirmed)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(ConfirmResetRecover::Intro, FlowMsg::Info) => { (Self::Intro, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(ConfirmResetRecover::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
} (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled),
(ConfirmResetRecover::Menu, FlowMsg::Cancelled) => { _ => self.do_nothing(),
Decision::Goto(ConfirmResetRecover::Intro, SwipeDirection::Right)
}
(ConfirmResetRecover::Menu, FlowMsg::Choice(0)) => Decision::Return(FlowMsg::Cancelled),
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings,
layout::obj::LayoutObj,
model_mercury::component::{PromptScreen, SwipeContent},
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_reset_recover( pub extern "C" fn new_confirm_reset_recover(
n_args: usize, n_args: usize,
@ -104,23 +97,9 @@ impl ConfirmResetRecover {
FrameMsg::Button(_) => Some(FlowMsg::Cancelled), FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
}); });
let content_confirm = Frame::left_aligned( let res = SwipeFlow::new(&ConfirmResetRecover::Intro)?
TR::reset__title_create_wallet.into(), .with_page(&ConfirmResetRecover::Intro, content_intro)?
SwipeContent::new(PromptScreen::new_hold_to_confirm()), .with_page(&ConfirmResetRecover::Menu, content_menu)?;
)
.with_footer(TR::instructions__hold_to_confirm.into(), None)
.with_swipe(SwipeDirection::Down, SwipeSettings::default())
.map(|msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
_ => Some(FlowMsg::Cancelled),
});
let store = flow_store()
.add(content_intro)?
.add(content_menu)?
.add(content_confirm)?;
let res = SwipeFlow::new(ConfirmResetRecover::Intro, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,14 +1,20 @@
use crate::{ use crate::{
error, error,
micropython::qstr::Qstr, micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, FlowMsg, FlowState, FlowStore}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
@ -19,7 +25,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum SetNewPin { pub enum SetNewPin {
Intro, Intro,
Menu, Menu,
@ -28,63 +34,37 @@ pub enum SetNewPin {
} }
impl FlowState for SetNewPin { impl FlowState for SetNewPin {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
match (self, direction) { fn index(&'static self) -> usize {
(SetNewPin::Intro, SwipeDirection::Left) => Decision::Goto(SetNewPin::Menu, direction), *self as usize
(SetNewPin::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed), }
(SetNewPin::Menu, SwipeDirection::Right) => Decision::Goto(SetNewPin::Intro, direction), fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
(SetNewPin::CancelPinIntro, SwipeDirection::Up) => { match (self, direction) {
Decision::Goto(SetNewPin::CancelPinConfirm, direction) (Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
} (Self::Intro, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
(SetNewPin::CancelPinIntro, SwipeDirection::Right) => { (Self::Menu, SwipeDirection::Right) => Self::Intro.swipe(direction),
Decision::Goto(SetNewPin::Intro, direction) (Self::CancelPinIntro, SwipeDirection::Up) => Self::CancelPinConfirm.swipe(direction),
} (Self::CancelPinIntro, SwipeDirection::Right) => Self::Intro.swipe(direction),
(SetNewPin::CancelPinConfirm, SwipeDirection::Down) => { (Self::CancelPinConfirm, SwipeDirection::Down) => Self::CancelPinIntro.swipe(direction),
Decision::Goto(SetNewPin::CancelPinIntro, direction) (Self::CancelPinConfirm, SwipeDirection::Right) => Self::Intro.swipe(direction),
} _ => self.do_nothing(),
(SetNewPin::CancelPinConfirm, SwipeDirection::Right) => {
Decision::Goto(SetNewPin::Intro, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(SetNewPin::Intro, FlowMsg::Info) => { (Self::Intro, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(SetNewPin::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(0)) => Self::CancelPinIntro.swipe_left(),
} (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
(SetNewPin::Menu, FlowMsg::Choice(0)) => { (Self::CancelPinIntro, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
Decision::Goto(SetNewPin::CancelPinIntro, SwipeDirection::Left) (Self::CancelPinConfirm, FlowMsg::Cancelled) => Self::CancelPinIntro.swipe_right(),
} (Self::CancelPinConfirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
(SetNewPin::Menu, FlowMsg::Cancelled) => { _ => self.do_nothing(),
Decision::Goto(SetNewPin::Intro, SwipeDirection::Right)
}
(SetNewPin::CancelPinIntro, FlowMsg::Cancelled) => {
Decision::Goto(SetNewPin::Intro, SwipeDirection::Right)
}
(SetNewPin::CancelPinConfirm, FlowMsg::Cancelled) => {
Decision::Goto(SetNewPin::CancelPinIntro, SwipeDirection::Right)
}
(SetNewPin::CancelPinConfirm, FlowMsg::Confirmed) => {
Decision::Return(FlowMsg::Cancelled)
}
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings,
flow::{flow_store, SwipeFlow},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_set_new_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_set_new_pin(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, SetNewPin::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, SetNewPin::new_obj) }
@ -155,12 +135,11 @@ impl SetNewPin {
_ => None, _ => None,
}); });
let store = flow_store() let res = SwipeFlow::new(&SetNewPin::Intro)?
.add(content_intro)? .with_page(&SetNewPin::Intro, content_intro)?
.add(content_menu)? .with_page(&SetNewPin::Menu, content_menu)?
.add(content_cancel_intro)? .with_page(&SetNewPin::CancelPinIntro, content_cancel_intro)?
.add(content_cancel_confirm)?; .with_page(&SetNewPin::CancelPinConfirm, content_cancel_confirm)?;
let res = SwipeFlow::new(SetNewPin::Intro, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,23 +1,29 @@
use crate::{ use crate::{
error, error,
micropython::{iter::IterBuf, qstr::Qstr}, micropython::{iter::IterBuf, map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
button_request::ButtonRequest, button_request::ButtonRequest,
component::{ButtonRequestExt, ComponentExt, SwipeDirection}, component::{swipe_detect::SwipeSettings, ButtonRequestExt, ComponentExt, SwipeDirection},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
use super::super::{ use super::{
component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg}, super::{
theme, component::{Frame, FrameMsg, PromptScreen, VerticalMenu, VerticalMenuChoiceMsg},
theme,
},
util::ShowInfoParams,
}; };
use super::util::ShowInfoParams; #[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)]
pub enum ConfirmSummary { pub enum ConfirmSummary {
Summary, Summary,
Hold, Hold,
@ -28,62 +34,40 @@ pub enum ConfirmSummary {
} }
impl FlowState for ConfirmSummary { impl FlowState for ConfirmSummary {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ConfirmSummary::Summary | ConfirmSummary::Hold, SwipeDirection::Left) => { (Self::Summary | Self::Hold, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(ConfirmSummary::Menu, direction) (Self::Summary, SwipeDirection::Up) => Self::Hold.swipe(direction),
(Self::Hold, SwipeDirection::Down) => Self::Summary.swipe(direction),
(Self::Menu, SwipeDirection::Right) => Self::Summary.swipe(direction),
(Self::Menu, SwipeDirection::Left) => Self::FeeInfo.swipe(direction),
(Self::AccountInfo | Self::FeeInfo | Self::CancelTap, SwipeDirection::Right) => {
Self::Menu.swipe(direction)
} }
(ConfirmSummary::Summary, SwipeDirection::Up) => { _ => self.do_nothing(),
Decision::Goto(ConfirmSummary::Hold, direction)
}
(ConfirmSummary::Hold, SwipeDirection::Down) => {
Decision::Goto(ConfirmSummary::Summary, direction)
}
(ConfirmSummary::Menu, SwipeDirection::Right) => {
Decision::Goto(ConfirmSummary::Summary, direction)
}
(ConfirmSummary::Menu, SwipeDirection::Left) => {
Decision::Goto(ConfirmSummary::FeeInfo, direction)
}
(
ConfirmSummary::AccountInfo | ConfirmSummary::FeeInfo | ConfirmSummary::CancelTap,
SwipeDirection::Right,
) => Decision::Goto(ConfirmSummary::Menu, direction),
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(_, FlowMsg::Info) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Left), (_, FlowMsg::Info) => Self::Menu.swipe_left(),
(ConfirmSummary::Hold, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Confirmed), (Self::Hold, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed),
(ConfirmSummary::Menu, FlowMsg::Choice(0)) => { (Self::Menu, FlowMsg::Choice(0)) => Self::FeeInfo.swipe_left(),
Decision::Goto(ConfirmSummary::FeeInfo, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(1)) => Self::AccountInfo.swipe_left(),
} (Self::Menu, FlowMsg::Choice(2)) => Self::CancelTap.swipe_left(),
(ConfirmSummary::Menu, FlowMsg::Choice(1)) => { (Self::Menu, FlowMsg::Cancelled) => Self::Summary.swipe_right(),
Decision::Goto(ConfirmSummary::AccountInfo, SwipeDirection::Left) (Self::CancelTap, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
} (_, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
(ConfirmSummary::Menu, FlowMsg::Choice(2)) => { _ => self.do_nothing(),
Decision::Goto(ConfirmSummary::CancelTap, SwipeDirection::Left)
}
(ConfirmSummary::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ConfirmSummary::Summary, SwipeDirection::Right)
}
(ConfirmSummary::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled),
(_, FlowMsg::Cancelled) => Decision::Goto(ConfirmSummary::Menu, SwipeDirection::Right),
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_confirm_summary(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmSummary::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmSummary::new_obj) }
@ -171,14 +155,14 @@ impl ConfirmSummary {
FrameMsg::Button(_) => Some(FlowMsg::Cancelled), FrameMsg::Button(_) => Some(FlowMsg::Cancelled),
}); });
let store = flow_store() let res = SwipeFlow::new(&ConfirmSummary::Summary)?
.add(content_summary)? .with_page(&ConfirmSummary::Summary, content_summary)?
.add(content_hold)? .with_page(&ConfirmSummary::Hold, content_hold)?
.add(content_menu)? .with_page(&ConfirmSummary::Menu, content_menu)?
.add(content_fee)? .with_page(&ConfirmSummary::FeeInfo, content_fee)?
.add(content_account)? .with_page(&ConfirmSummary::AccountInfo, content_account)?
.add(content_cancel_tap)?; .with_page(&ConfirmSummary::CancelTap, content_cancel_tap)?;
let res = SwipeFlow::new(ConfirmSummary::Summary, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,16 +1,21 @@
use crate::{ use crate::{
error, error,
micropython::{iter::IterBuf, qstr::Qstr}, micropython::{iter::IterBuf, map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
button_request::ButtonRequest, button_request::ButtonRequest,
component::{ component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphSource, Paragraphs}, text::paragraphs::{Paragraph, ParagraphSource, Paragraphs},
ButtonRequestExt, ComponentExt, Qr, SwipeDirection, ButtonRequestExt, ComponentExt, Qr, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
layout::util::ConfirmBlob, base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow, SwipePage,
},
layout::{obj::LayoutObj, util::ConfirmBlob},
model_mercury::component::SwipeContent,
}, },
}; };
@ -24,7 +29,7 @@ use super::super::{
const QR_BORDER: i16 = 4; const QR_BORDER: i16 = 4;
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum GetAddress { pub enum GetAddress {
Address, Address,
Tap, Tap,
@ -37,104 +42,48 @@ pub enum GetAddress {
} }
impl FlowState for GetAddress { impl FlowState for GetAddress {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(GetAddress::Address, SwipeDirection::Left) => { (Self::Address, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(GetAddress::Menu, direction) (Self::Address, SwipeDirection::Up) => Self::Tap.swipe(direction),
} (Self::Tap, SwipeDirection::Down) => Self::Address.swipe(direction),
(GetAddress::Address, SwipeDirection::Up) => Decision::Goto(GetAddress::Tap, direction), (Self::Tap, SwipeDirection::Left) => Self::Menu.swipe(direction),
(GetAddress::Tap, SwipeDirection::Down) => { (Self::Menu, SwipeDirection::Right) => Self::Address.swipe(direction),
Decision::Goto(GetAddress::Address, direction) (Self::QrCode, SwipeDirection::Right) => Self::Menu.swipe(direction),
} (Self::AccountInfo, SwipeDirection::Right) => Self::Menu.swipe_right(),
(GetAddress::Tap, SwipeDirection::Left) => Decision::Goto(GetAddress::Menu, direction), (Self::Cancel, SwipeDirection::Up) => Self::CancelTap.swipe(direction),
(GetAddress::Menu, SwipeDirection::Right) => { (Self::Cancel, SwipeDirection::Right) => Self::Menu.swipe(direction),
Decision::Goto(GetAddress::Address, direction) (Self::CancelTap, SwipeDirection::Down) => Self::Cancel.swipe(direction),
} (Self::CancelTap, SwipeDirection::Right) => Self::Menu.swipe(direction),
(GetAddress::QrCode, SwipeDirection::Right) => { _ => self.do_nothing(),
Decision::Goto(GetAddress::Menu, direction)
}
(GetAddress::AccountInfo, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
}
(GetAddress::Cancel, SwipeDirection::Up) => {
Decision::Goto(GetAddress::CancelTap, direction)
}
(GetAddress::Cancel, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Menu, direction)
}
(GetAddress::CancelTap, SwipeDirection::Down) => {
Decision::Goto(GetAddress::Cancel, direction)
}
(GetAddress::CancelTap, SwipeDirection::Right) => {
Decision::Goto(GetAddress::Menu, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(GetAddress::Address, FlowMsg::Info) => { (Self::Address, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(GetAddress::Menu, SwipeDirection::Left) (Self::Tap, FlowMsg::Confirmed) => Self::Confirmed.swipe_up(),
} (Self::Tap, FlowMsg::Info) => Self::Menu.swipe_left(),
(Self::Confirmed, _) => self.return_msg(FlowMsg::Confirmed),
(GetAddress::Tap, FlowMsg::Confirmed) => { (Self::Menu, FlowMsg::Choice(0)) => Self::QrCode.swipe_left(),
Decision::Goto(GetAddress::Confirmed, SwipeDirection::Up) (Self::Menu, FlowMsg::Choice(1)) => Self::AccountInfo.swipe_left(),
} (Self::Menu, FlowMsg::Choice(2)) => Self::Cancel.swipe_left(),
(Self::Menu, FlowMsg::Cancelled) => Self::Address.swipe_right(),
(GetAddress::Tap, FlowMsg::Info) => { (Self::QrCode, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
Decision::Goto(GetAddress::Menu, SwipeDirection::Left) (Self::AccountInfo, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
} (Self::Cancel, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
(Self::CancelTap, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
(GetAddress::Confirmed, _) => Decision::Return(FlowMsg::Confirmed), (Self::CancelTap, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
_ => self.do_nothing(),
(GetAddress::Menu, FlowMsg::Choice(0)) => {
Decision::Goto(GetAddress::QrCode, SwipeDirection::Left)
}
(GetAddress::Menu, FlowMsg::Choice(1)) => {
Decision::Goto(GetAddress::AccountInfo, SwipeDirection::Left)
}
(GetAddress::Menu, FlowMsg::Choice(2)) => {
Decision::Goto(GetAddress::Cancel, SwipeDirection::Left)
}
(GetAddress::Menu, FlowMsg::Cancelled) => {
Decision::Goto(GetAddress::Address, SwipeDirection::Right)
}
(GetAddress::QrCode, FlowMsg::Cancelled) => {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
}
(GetAddress::AccountInfo, FlowMsg::Cancelled) => {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
}
(GetAddress::Cancel, FlowMsg::Cancelled) => {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
}
(GetAddress::CancelTap, FlowMsg::Confirmed) => Decision::Return(FlowMsg::Cancelled),
(GetAddress::CancelTap, FlowMsg::Cancelled) => {
Decision::Goto(GetAddress::Menu, SwipeDirection::Right)
}
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings, flow::SwipePage, layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_get_address(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_get_address(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, GetAddress::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, GetAddress::new_obj) }
@ -271,16 +220,15 @@ impl GetAddress {
_ => None, _ => None,
}); });
let store = flow_store() let res = SwipeFlow::new(&GetAddress::Address)?
.add(content_address)? .with_page(&GetAddress::Address, content_address)?
.add(content_tap)? .with_page(&GetAddress::Tap, content_tap)?
.add(content_confirmed)? .with_page(&GetAddress::Confirmed, content_confirmed)?
.add(content_menu)? .with_page(&GetAddress::Menu, content_menu)?
.add(content_qr)? .with_page(&GetAddress::QrCode, content_qr)?
.add(content_account)? .with_page(&GetAddress::AccountInfo, content_account)?
.add(content_cancel_info)? .with_page(&GetAddress::Cancel, content_cancel_info)?
.add(content_cancel_tap)?; .with_page(&GetAddress::CancelTap, content_cancel_tap)?;
let res = SwipeFlow::new(GetAddress::Address, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,13 +1,20 @@
use crate::{ use crate::{
error, error,
micropython::{map::Map, obj::Obj, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, FlowMsg, FlowState, FlowStore}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
@ -18,7 +25,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum PromptBackup { pub enum PromptBackup {
Intro, Intro,
Menu, Menu,
@ -27,68 +34,39 @@ pub enum PromptBackup {
} }
impl FlowState for PromptBackup { impl FlowState for PromptBackup {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(PromptBackup::Intro, SwipeDirection::Left) => { (Self::Intro, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(PromptBackup::Menu, direction) (Self::Intro, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
(Self::Menu, SwipeDirection::Right) => Self::Intro.swipe(direction),
(Self::SkipBackupIntro, SwipeDirection::Up) => Self::SkipBackupConfirm.swipe(direction),
(Self::SkipBackupIntro, SwipeDirection::Right) => Self::Intro.swipe(direction),
(Self::SkipBackupConfirm, SwipeDirection::Down) => {
Self::SkipBackupIntro.swipe(direction)
} }
(PromptBackup::Intro, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed), (Self::SkipBackupConfirm, SwipeDirection::Right) => Self::Intro.swipe(direction),
_ => self.do_nothing(),
(PromptBackup::Menu, SwipeDirection::Right) => {
Decision::Goto(PromptBackup::Intro, direction)
}
(PromptBackup::SkipBackupIntro, SwipeDirection::Up) => {
Decision::Goto(PromptBackup::SkipBackupConfirm, direction)
}
(PromptBackup::SkipBackupIntro, SwipeDirection::Right) => {
Decision::Goto(PromptBackup::Intro, direction)
}
(PromptBackup::SkipBackupConfirm, SwipeDirection::Down) => {
Decision::Goto(PromptBackup::SkipBackupIntro, direction)
}
(PromptBackup::SkipBackupConfirm, SwipeDirection::Right) => {
Decision::Goto(PromptBackup::Intro, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(PromptBackup::Intro, FlowMsg::Info) => { (Self::Intro, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(PromptBackup::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(0)) => Self::SkipBackupIntro.swipe_left(),
} (Self::Menu, FlowMsg::Cancelled) => Self::Intro.swipe_right(),
(PromptBackup::Menu, FlowMsg::Choice(0)) => { (Self::SkipBackupIntro, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
Decision::Goto(PromptBackup::SkipBackupIntro, SwipeDirection::Left) (Self::SkipBackupConfirm, FlowMsg::Cancelled) => Self::SkipBackupIntro.swipe_right(),
} (Self::SkipBackupConfirm, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Cancelled),
(PromptBackup::Menu, FlowMsg::Cancelled) => { _ => self.do_nothing(),
Decision::Goto(PromptBackup::Intro, SwipeDirection::Right)
}
(PromptBackup::SkipBackupIntro, FlowMsg::Cancelled) => {
Decision::Goto(PromptBackup::Menu, SwipeDirection::Right)
}
(PromptBackup::SkipBackupConfirm, FlowMsg::Cancelled) => {
Decision::Goto(PromptBackup::SkipBackupIntro, SwipeDirection::Right)
}
(PromptBackup::SkipBackupConfirm, FlowMsg::Confirmed) => {
Decision::Return(FlowMsg::Cancelled)
}
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings,
flow::{flow_store, SwipeFlow},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_prompt_backup(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_prompt_backup(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, PromptBackup::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, PromptBackup::new_obj) }
@ -159,12 +137,11 @@ impl PromptBackup {
_ => None, _ => None,
}); });
let store = flow_store() let res = SwipeFlow::new(&PromptBackup::Intro)?
.add(content_intro)? .with_page(&PromptBackup::Intro, content_intro)?
.add(content_menu)? .with_page(&PromptBackup::Menu, content_menu)?
.add(content_skip_intro)? .with_page(&PromptBackup::SkipBackupIntro, content_skip_intro)?
.add(content_skip_confirm)?; .with_page(&PromptBackup::SkipBackupConfirm, content_skip_confirm)?;
let res = SwipeFlow::new(PromptBackup::Intro, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,15 +1,21 @@
use crate::{ use crate::{
error, error,
micropython::qstr::Qstr, micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
button_request::ButtonRequest, button_request::ButtonRequest,
component::{ component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ButtonRequestExt, ComponentExt, SwipeDirection, ButtonRequestExt, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
@ -21,7 +27,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum RequestNumber { pub enum RequestNumber {
Number, Number,
Menu, Menu,
@ -29,49 +35,32 @@ pub enum RequestNumber {
} }
impl FlowState for RequestNumber { impl FlowState for RequestNumber {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(RequestNumber::Number, SwipeDirection::Left) => { (Self::Number, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(RequestNumber::Menu, direction) (Self::Menu, SwipeDirection::Right) => Self::Number.swipe(direction),
} (Self::Info, SwipeDirection::Right) => Self::Menu.swipe(direction),
(RequestNumber::Menu, SwipeDirection::Right) => { _ => self.do_nothing(),
Decision::Goto(RequestNumber::Number, direction)
}
(RequestNumber::Info, SwipeDirection::Right) => {
Decision::Goto(RequestNumber::Menu, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(RequestNumber::Number, FlowMsg::Info) => { (Self::Number, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(RequestNumber::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(0)) => Self::Info.swipe_left(),
} (Self::Menu, FlowMsg::Cancelled) => Self::Number.swipe_right(),
(RequestNumber::Menu, FlowMsg::Choice(0)) => { (Self::Info, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
Decision::Goto(RequestNumber::Info, SwipeDirection::Left) (Self::Number, FlowMsg::Choice(n)) => self.return_msg(FlowMsg::Choice(n)),
} _ => self.do_nothing(),
(RequestNumber::Menu, FlowMsg::Cancelled) => {
Decision::Goto(RequestNumber::Number, SwipeDirection::Right)
}
(RequestNumber::Info, FlowMsg::Cancelled) => {
Decision::Goto(RequestNumber::Menu, SwipeDirection::Right)
}
(RequestNumber::Number, FlowMsg::Choice(n)) => Decision::Return(FlowMsg::Choice(n)),
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_request_number(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_request_number(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, RequestNumber::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, RequestNumber::new_obj) }
@ -143,11 +132,10 @@ impl RequestNumber {
_ => None, _ => None,
}); });
let store = flow_store() let res = SwipeFlow::new(&RequestNumber::Number)?
.add(content_number_input)? .with_page(&RequestNumber::Number, content_number_input)?
.add(content_menu)? .with_page(&RequestNumber::Menu, content_menu)?
.add(content_info)?; .with_page(&RequestNumber::Info, content_info)?;
let res = SwipeFlow::new(RequestNumber::Number, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -10,7 +10,10 @@ use crate::{
text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt},
ButtonRequestExt, ComponentExt, SwipeDirection, ButtonRequestExt, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj, layout::obj::LayoutObj,
model_mercury::component::SwipeContent, model_mercury::component::SwipeContent,
}, },
@ -22,7 +25,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum ShowShareWords { pub enum ShowShareWords {
Instruction, Instruction,
Words, Words,
@ -31,39 +34,28 @@ pub enum ShowShareWords {
} }
impl FlowState for ShowShareWords { impl FlowState for ShowShareWords {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ShowShareWords::Instruction, SwipeDirection::Up) => { (Self::Instruction, SwipeDirection::Up) => Self::Words.swipe(direction),
Decision::Goto(ShowShareWords::Words, direction) (Self::Confirm, SwipeDirection::Down) => Self::Words.swipe(direction),
} (Self::Words, SwipeDirection::Up) => Self::Confirm.swipe(direction),
(ShowShareWords::Confirm, SwipeDirection::Down) => { (Self::Words, SwipeDirection::Down) => Self::Instruction.swipe(direction),
Decision::Goto(ShowShareWords::Words, direction) (Self::CheckBackupIntro, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
} _ => self.do_nothing(),
(ShowShareWords::Words, SwipeDirection::Up) => {
Decision::Goto(ShowShareWords::Confirm, direction)
}
(ShowShareWords::Words, SwipeDirection::Down) => {
Decision::Goto(ShowShareWords::Instruction, direction)
}
(ShowShareWords::CheckBackupIntro, SwipeDirection::Up) => {
Decision::Return(FlowMsg::Confirmed)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(ShowShareWords::Words, FlowMsg::Cancelled) => { (Self::Words, FlowMsg::Cancelled) => Self::Instruction.swipe_down(),
Decision::Goto(ShowShareWords::Instruction, SwipeDirection::Down) (Self::Words, FlowMsg::Confirmed) => Self::Confirm.swipe_up(),
} (Self::Confirm, FlowMsg::Confirmed) => Self::CheckBackupIntro.swipe_up(),
(ShowShareWords::Words, FlowMsg::Confirmed) => { _ => self.do_nothing(),
Decision::Goto(ShowShareWords::Confirm, SwipeDirection::Up)
}
(ShowShareWords::Confirm, FlowMsg::Confirmed) => {
Decision::Goto(ShowShareWords::CheckBackupIntro, SwipeDirection::Up)
}
_ => Decision::Nothing,
} }
} }
} }
@ -131,12 +123,14 @@ impl ShowShareWords {
.with_swipe(SwipeDirection::Up, SwipeSettings::default()) .with_swipe(SwipeDirection::Up, SwipeSettings::default())
.map(|_| Some(FlowMsg::Confirmed)); .map(|_| Some(FlowMsg::Confirmed));
let store = flow_store() let res = SwipeFlow::new(&ShowShareWords::Instruction)?
.add(content_instruction)? .with_page(&ShowShareWords::Instruction, content_instruction)?
.add(content_words)? .with_page(&ShowShareWords::Words, content_words)?
.add(content_confirm)? .with_page(&ShowShareWords::Confirm, content_confirm)?
.add(content_check_backup_intro)?; .with_page(
let res = SwipeFlow::new(ShowShareWords::Instruction, store)?; &ShowShareWords::CheckBackupIntro,
content_check_backup_intro,
)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
error, error,
micropython::{map::Map, obj::Obj, util},
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
@ -7,7 +8,10 @@ use crate::{
text::paragraphs::{Paragraph, Paragraphs}, text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj, layout::obj::LayoutObj,
model_mercury::component::SwipeContent, model_mercury::component::SwipeContent,
}, },
@ -18,7 +22,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum ShowTutorial { pub enum ShowTutorial {
StepWelcome, StepWelcome,
StepBegin, StepBegin,
@ -32,76 +36,43 @@ pub enum ShowTutorial {
} }
impl FlowState for ShowTutorial { impl FlowState for ShowTutorial {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(ShowTutorial::StepBegin, SwipeDirection::Up) => { (Self::StepBegin, SwipeDirection::Up) => Self::StepNavigation.swipe(direction),
Decision::Goto(ShowTutorial::StepNavigation, direction) (Self::StepNavigation, SwipeDirection::Up) => Self::StepMenu.swipe(direction),
} (Self::StepNavigation, SwipeDirection::Down) => Self::StepBegin.swipe(direction),
(ShowTutorial::StepNavigation, SwipeDirection::Up) => { (Self::StepMenu, SwipeDirection::Up) => Self::StepHold.swipe(direction),
Decision::Goto(ShowTutorial::StepMenu, direction) (Self::StepMenu, SwipeDirection::Down) => Self::StepNavigation.swipe(direction),
} (Self::StepMenu, SwipeDirection::Left) => Self::Menu.swipe(direction),
(ShowTutorial::StepNavigation, SwipeDirection::Down) => { (Self::Menu, SwipeDirection::Left) => Self::DidYouKnow.swipe(direction),
Decision::Goto(ShowTutorial::StepBegin, direction) (Self::Menu, SwipeDirection::Right) => Self::StepBegin.swipe(direction),
} (Self::DidYouKnow, SwipeDirection::Right) => Self::Menu.swipe(direction),
(ShowTutorial::StepMenu, SwipeDirection::Up) => { (Self::StepDone, SwipeDirection::Up) => self.return_msg(FlowMsg::Confirmed),
Decision::Goto(ShowTutorial::StepHold, direction) _ => self.do_nothing(),
}
(ShowTutorial::StepMenu, SwipeDirection::Down) => {
Decision::Goto(ShowTutorial::StepNavigation, direction)
}
(ShowTutorial::StepMenu, SwipeDirection::Left) => {
Decision::Goto(ShowTutorial::Menu, direction)
}
(ShowTutorial::Menu, SwipeDirection::Left) => {
Decision::Goto(ShowTutorial::DidYouKnow, direction)
}
(ShowTutorial::Menu, SwipeDirection::Right) => {
Decision::Goto(ShowTutorial::StepBegin, direction)
}
(ShowTutorial::DidYouKnow, SwipeDirection::Right) => {
Decision::Goto(ShowTutorial::Menu, direction)
}
(ShowTutorial::StepDone, SwipeDirection::Up) => Decision::Return(FlowMsg::Confirmed),
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(ShowTutorial::StepWelcome, FlowMsg::Confirmed) => { (Self::StepWelcome, FlowMsg::Confirmed) => Self::StepBegin.swipe_up(),
Decision::Goto(ShowTutorial::StepBegin, SwipeDirection::Up) (Self::StepMenu, FlowMsg::Info) => Self::Menu.swipe_left(),
} (Self::Menu, FlowMsg::Choice(0)) => Self::DidYouKnow.swipe_left(),
(ShowTutorial::StepMenu, FlowMsg::Info) => { (Self::Menu, FlowMsg::Choice(1)) => Self::StepBegin.swipe_right(),
Decision::Goto(ShowTutorial::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(2)) => Self::HoldToExit.swipe_up(),
} (Self::Menu, FlowMsg::Cancelled) => Self::StepMenu.swipe_right(),
(ShowTutorial::Menu, FlowMsg::Choice(0)) => { (Self::DidYouKnow, FlowMsg::Cancelled) => Self::Menu.swipe_right(),
Decision::Goto(ShowTutorial::DidYouKnow, SwipeDirection::Left) (Self::StepHold, FlowMsg::Confirmed) => Self::StepDone.swipe_up(),
} (Self::HoldToExit, FlowMsg::Confirmed) => Self::StepDone.swipe_up(),
(ShowTutorial::Menu, FlowMsg::Choice(1)) => { _ => self.do_nothing(),
Decision::Goto(ShowTutorial::StepBegin, SwipeDirection::Right)
}
(ShowTutorial::Menu, FlowMsg::Choice(2)) => {
Decision::Goto(ShowTutorial::HoldToExit, SwipeDirection::Up)
}
(ShowTutorial::Menu, FlowMsg::Cancelled) => {
Decision::Goto(ShowTutorial::StepMenu, SwipeDirection::Right)
}
(ShowTutorial::DidYouKnow, FlowMsg::Cancelled) => {
Decision::Goto(ShowTutorial::Menu, SwipeDirection::Right)
}
(ShowTutorial::StepHold, FlowMsg::Confirmed) => {
Decision::Goto(ShowTutorial::StepDone, SwipeDirection::Up)
}
(ShowTutorial::HoldToExit, FlowMsg::Confirmed) => {
Decision::Goto(ShowTutorial::StepDone, SwipeDirection::Up)
}
_ => Decision::Nothing,
} }
} }
} }
use crate::micropython::{map::Map, obj::Obj, util};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_show_tutorial(_n_args: usize, _args: *const Obj, _kwargs: *mut Map) -> Obj { pub extern "C" fn new_show_tutorial(_n_args: usize, _args: *const Obj, _kwargs: *mut Map) -> Obj {
unsafe { util::try_or_raise(ShowTutorial::new_obj) } unsafe { util::try_or_raise(ShowTutorial::new_obj) }
@ -213,17 +184,16 @@ impl ShowTutorial {
.with_footer(TR::instructions__exit_tutorial.into(), None) .with_footer(TR::instructions__exit_tutorial.into(), None)
.map(|msg| matches!(msg, FrameMsg::Content(())).then_some(FlowMsg::Confirmed)); .map(|msg| matches!(msg, FrameMsg::Content(())).then_some(FlowMsg::Confirmed));
let store = flow_store() let res = SwipeFlow::new(&ShowTutorial::StepWelcome)?
.add(content_step_welcome)? .with_page(&ShowTutorial::StepWelcome, content_step_welcome)?
.add(content_step_begin)? .with_page(&ShowTutorial::StepBegin, content_step_begin)?
.add(content_step_navigation)? .with_page(&ShowTutorial::StepNavigation, content_step_navigation)?
.add(content_step_menu)? .with_page(&ShowTutorial::StepMenu, content_step_menu)?
.add(content_step_hold)? .with_page(&ShowTutorial::StepHold, content_step_hold)?
.add(content_step_done)? .with_page(&ShowTutorial::StepDone, content_step_done)?
.add(content_menu)? .with_page(&ShowTutorial::Menu, content_menu)?
.add(content_did_you_know)? .with_page(&ShowTutorial::DidYouKnow, content_did_you_know)?
.add(content_hold_to_exit)?; .with_page(&ShowTutorial::HoldToExit, content_hold_to_exit)?;
let res = SwipeFlow::new(ShowTutorial::StepWelcome, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -1,14 +1,20 @@
use crate::{ use crate::{
error, error,
micropython::qstr::Qstr, micropython::{map::Map, obj::Obj, qstr::Qstr, util},
strutil::TString, strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{ component::{
swipe_detect::SwipeSettings,
text::paragraphs::{Paragraph, ParagraphSource}, text::paragraphs::{Paragraph, ParagraphSource},
ComponentExt, SwipeDirection, ComponentExt, SwipeDirection,
}, },
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow}, flow::{
base::{DecisionBuilder as _, StateChange},
FlowMsg, FlowState, SwipeFlow,
},
layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
}, },
}; };
@ -17,7 +23,7 @@ use super::super::{
theme, theme,
}; };
#[derive(Copy, Clone, PartialEq, Eq, ToPrimitive)] #[derive(Copy, Clone, PartialEq, Eq)]
pub enum WarningHiPrio { pub enum WarningHiPrio {
Message, Message,
Menu, Menu,
@ -25,47 +31,32 @@ pub enum WarningHiPrio {
} }
impl FlowState for WarningHiPrio { impl FlowState for WarningHiPrio {
fn handle_swipe(&self, direction: SwipeDirection) -> Decision<Self> { #[inline]
fn index(&'static self) -> usize {
*self as usize
}
fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange {
match (self, direction) { match (self, direction) {
(WarningHiPrio::Message, SwipeDirection::Left) => { (Self::Message, SwipeDirection::Left) => Self::Menu.swipe(direction),
Decision::Goto(WarningHiPrio::Menu, direction) (Self::Message, SwipeDirection::Up) => Self::Cancelled.swipe(direction),
} (Self::Menu, SwipeDirection::Right) => Self::Message.swipe(direction),
(WarningHiPrio::Message, SwipeDirection::Up) => { _ => self.do_nothing(),
Decision::Goto(WarningHiPrio::Cancelled, direction)
}
(WarningHiPrio::Menu, SwipeDirection::Right) => {
Decision::Goto(WarningHiPrio::Message, direction)
}
_ => Decision::Nothing,
} }
} }
fn handle_event(&self, msg: FlowMsg) -> Decision<Self> { fn handle_event(&'static self, msg: FlowMsg) -> StateChange {
match (self, msg) { match (self, msg) {
(WarningHiPrio::Message, FlowMsg::Info) => { (Self::Message, FlowMsg::Info) => Self::Menu.swipe_left(),
Decision::Goto(WarningHiPrio::Menu, SwipeDirection::Left) (Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Confirmed),
} (Self::Menu, FlowMsg::Choice(_)) => Self::Cancelled.swipe_up(),
(WarningHiPrio::Menu, FlowMsg::Choice(1)) => Decision::Return(FlowMsg::Confirmed), (Self::Menu, FlowMsg::Cancelled) => Self::Message.swipe_right(),
(WarningHiPrio::Menu, FlowMsg::Choice(_)) => { (Self::Cancelled, _) => self.return_msg(FlowMsg::Cancelled),
Decision::Goto(WarningHiPrio::Cancelled, SwipeDirection::Up) _ => self.do_nothing(),
}
(WarningHiPrio::Menu, FlowMsg::Cancelled) => {
Decision::Goto(WarningHiPrio::Message, SwipeDirection::Right)
}
(WarningHiPrio::Cancelled, _) => Decision::Return(FlowMsg::Cancelled),
_ => Decision::Nothing,
} }
} }
} }
use crate::{
micropython::{map::Map, obj::Obj, util},
ui::{
component::swipe_detect::SwipeSettings, layout::obj::LayoutObj,
model_mercury::component::SwipeContent,
},
};
#[allow(clippy::not_unsafe_ptr_arg_deref)] #[allow(clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn new_warning_hi_prio(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { pub extern "C" fn new_warning_hi_prio(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, WarningHiPrio::new_obj) } unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, WarningHiPrio::new_obj) }
@ -118,11 +109,10 @@ impl WarningHiPrio {
.with_footer(TR::instructions__continue_in_app.into(), None) .with_footer(TR::instructions__continue_in_app.into(), None)
.map(|_| Some(FlowMsg::Cancelled)); .map(|_| Some(FlowMsg::Cancelled));
let store = flow_store() let res = SwipeFlow::new(&WarningHiPrio::Message)?
.add(content_message)? .with_page(&WarningHiPrio::Message, content_message)?
.add(content_menu)? .with_page(&WarningHiPrio::Menu, content_menu)?
.add(content_cancelled)?; .with_page(&WarningHiPrio::Cancelled, content_cancelled)?;
let res = SwipeFlow::new(WarningHiPrio::Message, store)?;
Ok(LayoutObj::new(res)?.into()) Ok(LayoutObj::new(res)?.into())
} }
} }

View File

@ -28,12 +28,12 @@ pub use canvas::{
}; };
pub use circle::Circle; pub use circle::Circle;
pub use corner_highlight::CornerHighlight; pub use corner_highlight::CornerHighlight;
pub use display::{render_on_canvas, render_on_display, unlock_bumps_on_failure}; pub use display::{render_on_canvas, render_on_display, unlock_bumps_on_failure, ConcreteRenderer};
#[cfg(feature = "ui_jpeg_decoder")] #[cfg(feature = "ui_jpeg_decoder")]
pub use jpeg::JpegImage; pub use jpeg::JpegImage;
pub use qrcode::QrImage; pub use qrcode::QrImage;
pub use rawimage::RawImage; pub use rawimage::RawImage;
pub use render::{DirectRenderer, ProgressiveRenderer, Renderer}; pub use render::{DirectRenderer, ProgressiveRenderer, Renderer, ScopedRenderer};
pub use text::Text; pub use text::Text;
pub use toif::ToifImage; pub use toif::ToifImage;
#[cfg(feature = "ui_image_buffer")] #[cfg(feature = "ui_image_buffer")]