mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-08-01 03:18:12 +00:00
refactor(core/ui): T3T1 flow animation
[no changelog]
This commit is contained in:
parent
08e83f23c7
commit
402e14dd0a
@ -72,7 +72,6 @@ impl crate::trace::Trace for Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BlendedImage {
|
pub struct BlendedImage {
|
||||||
bg: Icon,
|
bg: Icon,
|
||||||
fg: Icon,
|
fg: Icon,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use super::{Component, Event, EventCtx};
|
use super::{Component, Event, EventCtx};
|
||||||
use crate::ui::{geometry::Rect, shape::Renderer};
|
use crate::ui::{geometry::Rect, shape::Renderer};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MsgMap<T, F> {
|
pub struct MsgMap<T, F> {
|
||||||
inner: T,
|
inner: T,
|
||||||
func: F,
|
func: F,
|
||||||
@ -57,11 +56,11 @@ impl<T, F> crate::ui::flow::Swipable for MsgMap<T, F>
|
|||||||
where
|
where
|
||||||
T: Component + crate::ui::flow::Swipable,
|
T: Component + crate::ui::flow::Swipable,
|
||||||
{
|
{
|
||||||
fn can_swipe(&self, direction: crate::ui::component::SwipeDirection) -> bool {
|
fn swipe_start(&mut self, ctx: &mut EventCtx, direction: super::SwipeDirection) -> bool {
|
||||||
self.inner.can_swipe(direction)
|
self.inner.swipe_start(ctx, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swiped(&mut self, ctx: &mut EventCtx, direction: crate::ui::component::SwipeDirection) {
|
fn swipe_finished(&self) -> bool {
|
||||||
self.inner.swiped(ctx, direction)
|
self.inner.swipe_finished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ const CORNER_RADIUS: u8 = 4;
|
|||||||
const DARK: Color = Color::rgb(0, 0, 0);
|
const DARK: Color = Color::rgb(0, 0, 0);
|
||||||
const LIGHT: Color = Color::rgb(0xff, 0xff, 0xff);
|
const LIGHT: Color = Color::rgb(0xff, 0xff, 0xff);
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Qr {
|
pub struct Qr {
|
||||||
text: String<MAX_DATA>,
|
text: String<MAX_DATA>,
|
||||||
border: i16,
|
border: i16,
|
||||||
|
@ -7,7 +7,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Timeout {
|
pub struct Timeout {
|
||||||
time_ms: u32,
|
time_ms: u32,
|
||||||
timer: Option<TimerToken>,
|
timer: Option<TimerToken>,
|
||||||
|
@ -16,18 +16,21 @@ impl SwipeDirection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Component must implement this trait in order to be part of swipe-based flow.
|
/// Component must implement this trait in order to be part of swipe-based flow.
|
||||||
/// The process of receiving a swipe is two-step, because in order to render the
|
///
|
||||||
/// transition animation Flow makes a copy of the pre-swipe state of the
|
/// Default implementation ignores every swipe.
|
||||||
/// component to render it along with the post-swipe state.
|
|
||||||
pub trait Swipable {
|
pub trait Swipable {
|
||||||
/// Return true if component can handle swipe in a given direction.
|
/// Attempt a swipe. Return false if component in its current state doesn't
|
||||||
fn can_swipe(&self, _direction: SwipeDirection) -> bool {
|
/// accept a swipe in the given direction. Start a transition animation
|
||||||
|
/// if true is returned.
|
||||||
|
fn swipe_start(&mut self, _ctx: &mut EventCtx, _direction: SwipeDirection) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make component react to swipe event. Only called if component returned
|
/// Return true when transition animation is finished. SwipeFlow needs to
|
||||||
/// true in the previous function.
|
/// know this in order to resume normal input processing.
|
||||||
fn swiped(&mut self, _ctx: &mut EventCtx, _direction: SwipeDirection) {}
|
fn swipe_finished(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Component::Msg for component parts of a flow. Converting results of
|
/// Component::Msg for component parts of a flow. Converting results of
|
||||||
@ -68,7 +71,7 @@ impl<Q> Decision<Q> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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. Roughly the C in MVC.
|
||||||
pub trait FlowState
|
pub trait FlowState
|
||||||
where
|
where
|
||||||
Self: Sized + Copy + Eq + ToPrimitive,
|
Self: Sized + Copy + Eq + ToPrimitive,
|
||||||
|
@ -23,21 +23,28 @@ pub struct SwipeFlow<Q, S> {
|
|||||||
state: Q,
|
state: Q,
|
||||||
/// FlowStore with all screens/components.
|
/// FlowStore with all screens/components.
|
||||||
store: S,
|
store: S,
|
||||||
/// `Some` when state transition animation is in progress.
|
/// `Transition::None` when state transition animation is in not progress.
|
||||||
transition: Option<Transition<Q>>,
|
transition: Transition<Q>,
|
||||||
/// Swipe detector.
|
/// Swipe detector.
|
||||||
swipe: Swipe,
|
swipe: Swipe,
|
||||||
/// Animation parameter.
|
/// Animation parameter.
|
||||||
anim_offset: Offset,
|
anim_offset: Offset,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Transition<Q> {
|
enum Transition<Q> {
|
||||||
/// State we are transitioning _from_.
|
/// SwipeFlow is performing transition between different states.
|
||||||
prev_state: Q,
|
External {
|
||||||
/// Animation progress.
|
/// State we are transitioning _from_.
|
||||||
animation: Animation<Offset>,
|
prev_state: Q,
|
||||||
/// Direction of the slide animation.
|
/// Animation progress.
|
||||||
direction: SwipeDirection,
|
animation: Animation<Offset>,
|
||||||
|
/// Direction of the slide animation.
|
||||||
|
direction: SwipeDirection,
|
||||||
|
},
|
||||||
|
/// Transition runs in child component, we forward events and wait.
|
||||||
|
Internal,
|
||||||
|
/// No transition.
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
|
impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
|
||||||
@ -45,14 +52,18 @@ impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
state: init,
|
state: init,
|
||||||
store,
|
store,
|
||||||
transition: None,
|
transition: Transition::None,
|
||||||
swipe: Swipe::new().down().up().left().right(),
|
swipe: Swipe::new().down().up().left().right(),
|
||||||
anim_offset: Offset::zero(),
|
anim_offset: Offset::zero(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn goto(&mut self, ctx: &mut EventCtx, direction: SwipeDirection, state: Q) {
|
fn goto(&mut self, ctx: &mut EventCtx, direction: SwipeDirection, state: Q) {
|
||||||
self.transition = Some(Transition {
|
if state == self.state {
|
||||||
|
self.transition = Transition::Internal;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.transition = Transition::External {
|
||||||
prev_state: self.state,
|
prev_state: self.state,
|
||||||
animation: Animation::new(
|
animation: Animation::new(
|
||||||
Offset::zero(),
|
Offset::zero(),
|
||||||
@ -61,58 +72,71 @@ impl<Q: FlowState, S: FlowStore> SwipeFlow<Q, S> {
|
|||||||
Instant::now(),
|
Instant::now(),
|
||||||
),
|
),
|
||||||
direction,
|
direction,
|
||||||
});
|
};
|
||||||
self.state = state;
|
self.state = state;
|
||||||
ctx.request_anim_frame();
|
ctx.request_anim_frame();
|
||||||
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: Q, target: &mut impl Renderer<'s>) {
|
||||||
self.store.render(state.index(), target)
|
self.store.render(state.index(), target)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_transition<'s>(&'s self, transition: &Transition<Q>, target: &mut impl Renderer<'s>) {
|
fn render_transition<'s>(
|
||||||
let off = transition.animation.value(Instant::now());
|
&'s self,
|
||||||
|
prev_state: &Q,
|
||||||
if self.state == transition.prev_state {
|
animation: &Animation<Offset>,
|
||||||
target.with_origin(off, &|target| {
|
direction: &SwipeDirection,
|
||||||
self.store.render_cloned(target);
|
target: &mut impl Renderer<'s>,
|
||||||
});
|
) {
|
||||||
} else {
|
let off = animation.value(Instant::now());
|
||||||
target.with_origin(off, &|target| {
|
target.with_origin(off, &|target| {
|
||||||
self.render_state(transition.prev_state, target);
|
self.render_state(*prev_state, target);
|
||||||
});
|
});
|
||||||
}
|
target.with_origin(off - direction.as_offset(self.anim_offset), &|target| {
|
||||||
target.with_origin(
|
self.render_state(self.state, target);
|
||||||
off - transition.direction.as_offset(self.anim_offset),
|
});
|
||||||
&|target| {
|
|
||||||
self.render_state(self.state, target);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_transition(&mut self, ctx: &mut EventCtx) {
|
fn handle_transition(&mut self, ctx: &mut EventCtx, event: Event) -> Option<FlowMsg> {
|
||||||
if let Some(transition) = &self.transition {
|
let i = self.state.index();
|
||||||
if transition.animation.finished(Instant::now()) {
|
let mut finished = false;
|
||||||
self.transition = None;
|
let result = match &self.transition {
|
||||||
unwrap!(self.store.clone(None)); // Free the clone.
|
Transition::External { animation, .. }
|
||||||
|
if matches!(event, Event::Timer(EventCtx::ANIM_FRAME_TIMER)) =>
|
||||||
let msg = self.store.event(self.state.index(), ctx, Event::Attach);
|
{
|
||||||
assert!(msg.is_none())
|
if animation.finished(Instant::now()) {
|
||||||
} else {
|
finished = true;
|
||||||
ctx.request_anim_frame();
|
ctx.request_paint();
|
||||||
|
self.store.event(i, ctx, Event::Attach)
|
||||||
|
} else {
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
ctx.request_paint();
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ctx.request_paint();
|
Transition::External { .. } => None, // ignore all events until animation finishes
|
||||||
|
Transition::Internal => {
|
||||||
|
let msg = self.store.event(i, ctx, event);
|
||||||
|
if self.store.map_swipable(i, |s| s.swipe_finished()) {
|
||||||
|
finished = true;
|
||||||
|
};
|
||||||
|
msg
|
||||||
|
}
|
||||||
|
Transition::None => unreachable!(),
|
||||||
|
};
|
||||||
|
if finished {
|
||||||
|
self.transition = Transition::None;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_swipe_child(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> Decision<Q> {
|
fn handle_swipe_child(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> Decision<Q> {
|
||||||
let i = self.state.index();
|
let i = self.state.index();
|
||||||
if self.store.map_swipable(i, |s| s.can_swipe(direction)) {
|
if self
|
||||||
// Before handling the swipe we make a copy of the original state so that we
|
.store
|
||||||
// can render both states in the transition animation.
|
.map_swipable(i, |s| s.swipe_start(ctx, direction))
|
||||||
unwrap!(self.store.clone(Some(i)));
|
{
|
||||||
self.store.map_swipable(i, |s| s.swiped(ctx, direction));
|
|
||||||
Decision::Goto(self.state, direction)
|
Decision::Goto(self.state, direction)
|
||||||
} else {
|
} else {
|
||||||
Decision::Nothing
|
Decision::Nothing
|
||||||
@ -142,14 +166,8 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
// TODO: are there any events we want to send to all? timers perhaps?
|
if !matches!(self.transition, Transition::None) {
|
||||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
return self.handle_transition(ctx, event);
|
||||||
self.handle_transition(ctx);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Ignore events while transition is running.
|
|
||||||
if self.transition.is_some() {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut decision = Decision::Nothing;
|
let mut decision = Decision::Nothing;
|
||||||
@ -173,10 +191,13 @@ impl<Q: FlowState, S: FlowStore> Component for SwipeFlow<Q, S> {
|
|||||||
fn paint(&mut self) {}
|
fn paint(&mut self) {}
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
if let Some(transition) = &self.transition {
|
match &self.transition {
|
||||||
self.render_transition(transition, target)
|
Transition::None | Transition::Internal => self.render_state(self.state, target),
|
||||||
} else {
|
Transition::External {
|
||||||
self.render_state(self.state, target)
|
prev_state,
|
||||||
|
animation,
|
||||||
|
direction,
|
||||||
|
} => self.render_transition(prev_state, animation, direction, target),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,101 @@
|
|||||||
use crate::ui::{
|
use crate::{
|
||||||
component::{Component, Event, EventCtx, Paginate, SwipeDirection},
|
micropython::gc::Gc,
|
||||||
flow::base::Swipable,
|
time::{Duration, Instant},
|
||||||
geometry::{Axis, Rect},
|
ui::{
|
||||||
shape::Renderer,
|
animation::Animation,
|
||||||
|
component::{Component, Event, EventCtx, Paginate, SwipeDirection},
|
||||||
|
flow::base::Swipable,
|
||||||
|
geometry::{Axis, Offset, Rect},
|
||||||
|
shape::Renderer,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct Transition<T> {
|
||||||
|
/// Clone of the component before page change.
|
||||||
|
cloned: Gc<T>,
|
||||||
|
/// Animation progress.
|
||||||
|
animation: Animation<Offset>,
|
||||||
|
/// Direction of the slide animation.
|
||||||
|
direction: SwipeDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ANIMATION_DURATION: Duration = Duration::from_millis(333);
|
||||||
|
|
||||||
/// Allows any implementor of `Paginate` to be part of `Swipable` UI flow.
|
/// Allows any implementor of `Paginate` to be part of `Swipable` UI flow.
|
||||||
#[derive(Clone)]
|
/// Renders sliding animation when changing pages.
|
||||||
pub struct SwipePage<T> {
|
pub struct SwipePage<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
|
bounds: Rect,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
pages: usize,
|
pages: usize,
|
||||||
current: usize,
|
current: usize,
|
||||||
|
transition: Option<Transition<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SwipePage<T> {
|
impl<T: Component + Paginate + Clone> SwipePage<T> {
|
||||||
pub fn vertical(inner: T) -> Self {
|
pub fn vertical(inner: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner,
|
inner,
|
||||||
|
bounds: Rect::zero(),
|
||||||
axis: Axis::Vertical,
|
axis: Axis::Vertical,
|
||||||
pages: 1,
|
pages: 1,
|
||||||
current: 0,
|
current: 0,
|
||||||
|
transition: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_transition(ctx: &mut EventCtx, event: Event, transition: &mut Transition<T>) -> bool {
|
||||||
|
let mut finished = false;
|
||||||
|
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||||
|
if transition.animation.finished(Instant::now()) {
|
||||||
|
finished = true;
|
||||||
|
} else {
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
}
|
||||||
|
ctx.request_paint()
|
||||||
|
}
|
||||||
|
finished
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_transition<'s>(
|
||||||
|
&'s self,
|
||||||
|
transition: &'s Transition<T>,
|
||||||
|
target: &mut impl Renderer<'s>,
|
||||||
|
) {
|
||||||
|
let off = transition.animation.value(Instant::now());
|
||||||
|
target.in_clip(self.bounds, &|target| {
|
||||||
|
target.with_origin(off, &|target| {
|
||||||
|
transition.cloned.render(target);
|
||||||
|
});
|
||||||
|
target.with_origin(
|
||||||
|
off - transition.direction.as_offset(self.bounds.size()),
|
||||||
|
&|target| {
|
||||||
|
self.inner.render(target);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component + Paginate> Component for SwipePage<T> {
|
impl<T: Component + Paginate + Clone> Component for SwipePage<T> {
|
||||||
type Msg = T::Msg;
|
type Msg = T::Msg;
|
||||||
|
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
let result = self.inner.place(bounds);
|
self.bounds = self.inner.place(bounds);
|
||||||
self.pages = self.inner.page_count();
|
self.pages = self.inner.page_count();
|
||||||
result
|
self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
let msg = self.inner.event(ctx, event);
|
if let Some(t) = &mut self.transition {
|
||||||
msg
|
let finished = Self::handle_transition(ctx, event, t);
|
||||||
|
if finished {
|
||||||
|
// FIXME: how to ensure the Gc allocation is returned?
|
||||||
|
self.transition = None
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.inner.event(ctx, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint(&mut self) {
|
fn paint(&mut self) {
|
||||||
@ -44,33 +103,49 @@ impl<T: Component + Paginate> Component for SwipePage<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
|
if let Some(t) = &self.transition {
|
||||||
|
return self.render_transition(t, target);
|
||||||
|
}
|
||||||
self.inner.render(target)
|
self.inner.render(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component + Paginate> Swipable for SwipePage<T> {
|
impl<T: Component + Paginate + Clone> Swipable for SwipePage<T> {
|
||||||
fn can_swipe(&self, direction: SwipeDirection) -> bool {
|
fn swipe_start(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) -> bool {
|
||||||
match (self.axis, direction) {
|
match (self.axis, direction) {
|
||||||
(Axis::Horizontal, SwipeDirection::Up | SwipeDirection::Down) => false,
|
// Wrong direction
|
||||||
(Axis::Vertical, SwipeDirection::Left | SwipeDirection::Right) => false,
|
(Axis::Horizontal, SwipeDirection::Up | SwipeDirection::Down) => return false,
|
||||||
(_, SwipeDirection::Left | SwipeDirection::Up) => self.current + 1 < self.pages,
|
(Axis::Vertical, SwipeDirection::Left | SwipeDirection::Right) => return false,
|
||||||
(_, SwipeDirection::Right | SwipeDirection::Down) => self.current > 0,
|
// Begin
|
||||||
}
|
(_, SwipeDirection::Right | SwipeDirection::Down) if self.current == 0 => return false,
|
||||||
|
// End
|
||||||
|
(_, SwipeDirection::Left | SwipeDirection::Up) if self.current + 1 >= self.pages => {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
self.transition = Some(Transition {
|
||||||
|
cloned: unwrap!(Gc::new(self.inner.clone())),
|
||||||
|
animation: Animation::new(
|
||||||
|
Offset::zero(),
|
||||||
|
direction.as_offset(self.bounds.size()),
|
||||||
|
ANIMATION_DURATION,
|
||||||
|
Instant::now(),
|
||||||
|
),
|
||||||
|
direction,
|
||||||
|
});
|
||||||
|
self.current = match direction {
|
||||||
|
SwipeDirection::Left | SwipeDirection::Up => (self.current + 1).min(self.pages - 1),
|
||||||
|
SwipeDirection::Right | SwipeDirection::Down => self.current.saturating_sub(1),
|
||||||
|
};
|
||||||
|
self.inner.change_page(self.current);
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
ctx.request_paint();
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swiped(&mut self, ctx: &mut EventCtx, direction: SwipeDirection) {
|
fn swipe_finished(&self) -> bool {
|
||||||
match (self.axis, direction) {
|
self.transition.is_none()
|
||||||
(Axis::Horizontal, SwipeDirection::Up | SwipeDirection::Down) => return,
|
|
||||||
(Axis::Vertical, SwipeDirection::Left | SwipeDirection::Right) => return,
|
|
||||||
(_, SwipeDirection::Left | SwipeDirection::Up) => {
|
|
||||||
self.current = (self.current + 1).min(self.pages - 1);
|
|
||||||
}
|
|
||||||
(_, SwipeDirection::Right | SwipeDirection::Down) => {
|
|
||||||
self.current = self.current.saturating_sub(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.inner.change_page(self.current);
|
|
||||||
ctx.request_paint();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +160,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Make any component swipable by ignoring all swipe events.
|
/// Make any component swipable by ignoring all swipe events.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct IgnoreSwipe<T>(T);
|
pub struct IgnoreSwipe<T>(T);
|
||||||
|
|
||||||
impl<T> IgnoreSwipe<T> {
|
impl<T> IgnoreSwipe<T> {
|
||||||
@ -114,12 +188,7 @@ impl<T: Component> Component for IgnoreSwipe<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Swipable for IgnoreSwipe<T> {
|
impl<T> Swipable for IgnoreSwipe<T> {}
|
||||||
fn can_swipe(&self, _direction: SwipeDirection) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn swiped(&mut self, _ctx: &mut EventCtx, _direction: SwipeDirection) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
impl<T> crate::trace::Trace for IgnoreSwipe<T>
|
impl<T> crate::trace::Trace for IgnoreSwipe<T>
|
||||||
|
@ -14,9 +14,7 @@ use crate::micropython::gc::Gc;
|
|||||||
/// `FlowStore` is essentially `Vec<Gc<dyn Component + Swipable>>` except that
|
/// `FlowStore` is essentially `Vec<Gc<dyn Component + Swipable>>` except that
|
||||||
/// `trait Component` is not object-safe so it ends up being a kind of
|
/// `trait Component` is not object-safe so it ends up being a kind of
|
||||||
/// recursively-defined tuple.
|
/// recursively-defined tuple.
|
||||||
///
|
/// Implementors are something like the V in MVC.
|
||||||
/// Additionally the store makes it possible to make a clone of one of its
|
|
||||||
/// items, in order to make it possible to render transition animations.
|
|
||||||
pub trait FlowStore {
|
pub trait FlowStore {
|
||||||
/// Call `Component::place` on all elements.
|
/// Call `Component::place` on all elements.
|
||||||
fn place(&mut self, bounds: Rect) -> Rect;
|
fn place(&mut self, bounds: Rect) -> Rect;
|
||||||
@ -34,14 +32,8 @@ pub trait FlowStore {
|
|||||||
/// Forward `Swipable` methods to i-th element.
|
/// Forward `Swipable` methods to i-th element.
|
||||||
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T;
|
fn map_swipable<T>(&mut self, i: usize, func: impl FnOnce(&mut dyn Swipable) -> T) -> T;
|
||||||
|
|
||||||
/// Make a clone of i-th element, or free all clones if None is given.
|
|
||||||
fn clone(&mut self, i: Option<usize>) -> Result<(), error::Error>;
|
|
||||||
|
|
||||||
/// Call `Component::render` on the cloned element.
|
|
||||||
fn render_cloned<'s>(&'s self, target: &mut impl Renderer<'s>);
|
|
||||||
|
|
||||||
/// Add a Component to the end of a `FlowStore`.
|
/// Add a Component to the end of a `FlowStore`.
|
||||||
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable + Clone>(
|
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
|
||||||
self,
|
self,
|
||||||
elem: E,
|
elem: E,
|
||||||
) -> Result<impl FlowStore, error::Error>
|
) -> Result<impl FlowStore, error::Error>
|
||||||
@ -80,12 +72,7 @@ impl FlowStore for FlowEmpty {
|
|||||||
panic!()
|
panic!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone(&mut self, _i: Option<usize>) -> Result<(), error::Error> {
|
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn render_cloned<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
|
|
||||||
|
|
||||||
fn add<E: Component<Msg = FlowMsg> + MaybeTrace + Swipable + Clone>(
|
|
||||||
self,
|
self,
|
||||||
elem: E,
|
elem: E,
|
||||||
) -> Result<impl FlowStore, error::Error>
|
) -> Result<impl FlowStore, error::Error>
|
||||||
@ -94,7 +81,6 @@ impl FlowStore for FlowEmpty {
|
|||||||
{
|
{
|
||||||
Ok(FlowComponent {
|
Ok(FlowComponent {
|
||||||
elem: Gc::new(elem)?,
|
elem: Gc::new(elem)?,
|
||||||
cloned: None,
|
|
||||||
next: Self,
|
next: Self,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -104,9 +90,6 @@ struct FlowComponent<E: Component<Msg = FlowMsg>, P> {
|
|||||||
/// Component allocated on micropython heap.
|
/// Component allocated on micropython heap.
|
||||||
pub elem: Gc<E>,
|
pub elem: Gc<E>,
|
||||||
|
|
||||||
/// Clone.
|
|
||||||
pub cloned: Option<Gc<E>>,
|
|
||||||
|
|
||||||
/// Nested FlowStore.
|
/// Nested FlowStore.
|
||||||
pub next: P,
|
pub next: P,
|
||||||
}
|
}
|
||||||
@ -125,7 +108,7 @@ impl<E: Component<Msg = FlowMsg>, P> FlowComponent<E, P> {
|
|||||||
|
|
||||||
impl<E, P> FlowStore for FlowComponent<E, P>
|
impl<E, P> FlowStore for FlowComponent<E, P>
|
||||||
where
|
where
|
||||||
E: Component<Msg = FlowMsg> + MaybeTrace + Swipable + Clone,
|
E: Component<Msg = FlowMsg> + MaybeTrace + Swipable,
|
||||||
P: FlowStore,
|
P: FlowStore,
|
||||||
{
|
{
|
||||||
fn place(&mut self, bounds: Rect) -> Rect {
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
@ -167,33 +150,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone(&mut self, i: Option<usize>) -> Result<(), error::Error> {
|
fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable>(
|
||||||
match i {
|
|
||||||
None => {
|
|
||||||
// FIXME: how to ensure the allocation is returned?
|
|
||||||
self.cloned = None;
|
|
||||||
self.next.clone(None)?
|
|
||||||
}
|
|
||||||
Some(0) => {
|
|
||||||
self.cloned = Some(Gc::new(self.as_ref().clone())?);
|
|
||||||
self.next.clone(None)?
|
|
||||||
}
|
|
||||||
Some(i) => {
|
|
||||||
self.cloned = None;
|
|
||||||
self.next.clone(Some(i - 1))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_cloned<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
|
||||||
if let Some(cloned) = &self.cloned {
|
|
||||||
cloned.render(target)
|
|
||||||
}
|
|
||||||
self.next.render_cloned(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add<F: Component<Msg = FlowMsg> + MaybeTrace + Swipable + Clone>(
|
|
||||||
self,
|
self,
|
||||||
elem: F,
|
elem: F,
|
||||||
) -> Result<impl FlowStore, error::Error>
|
) -> Result<impl FlowStore, error::Error>
|
||||||
@ -202,7 +159,6 @@ where
|
|||||||
{
|
{
|
||||||
Ok(FlowComponent {
|
Ok(FlowComponent {
|
||||||
elem: self.elem,
|
elem: self.elem,
|
||||||
cloned: None,
|
|
||||||
next: self.next.add(elem)?,
|
next: self.next.add(elem)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct IconDialog<U> {
|
pub struct IconDialog<U> {
|
||||||
image: Child<BlendedImage>,
|
image: Child<BlendedImage>,
|
||||||
paragraphs: Paragraphs<ParagraphVecShort<'static>>,
|
paragraphs: Paragraphs<ParagraphVecShort<'static>>,
|
||||||
|
@ -250,11 +250,15 @@ impl<T> crate::ui::flow::Swipable for Frame<T>
|
|||||||
where
|
where
|
||||||
T: Component + crate::ui::flow::Swipable,
|
T: Component + crate::ui::flow::Swipable,
|
||||||
{
|
{
|
||||||
fn can_swipe(&self, direction: crate::ui::component::SwipeDirection) -> bool {
|
fn swipe_start(
|
||||||
self.inner().can_swipe(direction)
|
&mut self,
|
||||||
|
ctx: &mut EventCtx,
|
||||||
|
direction: crate::ui::component::SwipeDirection,
|
||||||
|
) -> bool {
|
||||||
|
self.update_content(ctx, |ctx, inner| inner.swipe_start(ctx, direction))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swiped(&mut self, ctx: &mut EventCtx, direction: crate::ui::component::SwipeDirection) {
|
fn swipe_finished(&self) -> bool {
|
||||||
self.update_content(ctx, |ctx, inner| inner.swiped(ctx, direction))
|
self.inner().swipe_finished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user