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