1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-18 19:31:04 +00:00

fix(core/mercury): improve share words swiping animation

This commit is contained in:
tychovrahe 2024-07-26 10:34:47 +02:00 committed by TychoVrahe
parent 008490bf4b
commit a15dd6598f
4 changed files with 72 additions and 67 deletions

View File

@ -0,0 +1 @@
[T3T1] Improve share words swiping animation

View File

@ -1,25 +1,25 @@
use super::{theme, InternallySwipableContent}; use super::{theme, InternallySwipableContent};
use crate::{ use crate::{
strutil::TString, strutil::TString,
time::Duration,
translations::TR, translations::TR,
ui::{ ui::{
animation::Animation,
component::{ component::{
base::AttachType,
swipe_detect::{SwipeConfig, SwipeSettings}, swipe_detect::{SwipeConfig, SwipeSettings},
Component, Event, EventCtx, Never, SwipeDirection, Component, Event, EventCtx, Never, SwipeDirection,
}, },
event::SwipeEvent, event::SwipeEvent,
geometry::{Alignment, Alignment2D, Insets, Offset, Rect}, geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
model_mercury::component::{Frame, FrameMsg, InternallySwipable}, model_mercury::component::{
swipe_content::SwipeAttachAnimation, Frame, FrameMsg, InternallySwipable,
},
shape::{self, Renderer}, shape::{self, Renderer},
util,
}, },
}; };
use heapless::Vec; use heapless::Vec;
const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less const MAX_WORDS: usize = 33; // super-shamir has 33 words, all other have less
const ANIMATION_DURATION_MS: Duration = Duration::from_millis(166);
type IndexVec = Vec<u8, MAX_WORDS>; type IndexVec = Vec<u8, MAX_WORDS>;
/// Component showing mnemonic/share words during backup procedure. Model T3T1 /// Component showing mnemonic/share words during backup procedure. Model T3T1
@ -133,9 +133,9 @@ struct ShareWordsInner<'a> {
next_index: i16, next_index: i16,
/// Area reserved for a shown word from mnemonic/share /// Area reserved for a shown word from mnemonic/share
area_word: Rect, area_word: Rect,
/// `Some` when transition animation is in progress
animation: Option<Animation<f32>>,
progress: i16, progress: i16,
attach_animation: SwipeAttachAnimation,
wait_for_attach: bool,
} }
impl<'a> ShareWordsInner<'a> { impl<'a> ShareWordsInner<'a> {
@ -147,8 +147,9 @@ impl<'a> ShareWordsInner<'a> {
page_index: 0, page_index: 0,
next_index: 0, next_index: 0,
area_word: Rect::zero(), area_word: Rect::zero(),
animation: None,
progress: 0, progress: 0,
attach_animation: SwipeAttachAnimation::new(),
wait_for_attach: false,
} }
} }
@ -175,6 +176,27 @@ impl<'a> ShareWordsInner<'a> {
.render(target); .render(target);
}); });
} }
fn should_animate_progress(&self) -> (SwipeDirection, bool) {
let (dir, should_animate) = if self.page_index < self.next_index {
(SwipeDirection::Up, !self.is_final_page())
} else {
(SwipeDirection::Down, !self.is_first_page())
};
(dir, should_animate)
}
fn should_animate_attach(&self, event: Event) -> (SwipeDirection, bool) {
match event {
Event::Attach(AttachType::Swipe(SwipeDirection::Up)) => {
(SwipeDirection::Up, !self.is_first_page())
}
Event::Attach(AttachType::Swipe(SwipeDirection::Down)) => {
(SwipeDirection::Down, !self.is_final_page())
}
_ => (SwipeDirection::Up, false),
}
}
} }
impl<'a> Component for ShareWordsInner<'a> { impl<'a> Component for ShareWordsInner<'a> {
@ -197,19 +219,29 @@ impl<'a> Component for ShareWordsInner<'a> {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
ctx.set_page_count(self.share_words.len()); ctx.set_page_count(self.share_words.len());
let (_, should_animate) = self.should_animate_attach(event);
self.attach_animation.lazy_start(ctx, event, should_animate);
match event { match event {
Event::Attach(_) => { Event::Attach(_) => {
self.progress = 0; self.progress = 0;
if !should_animate {
self.wait_for_attach = false;
}
} }
Event::Swipe(SwipeEvent::End(dir)) => match dir { Event::Swipe(SwipeEvent::End(dir)) => match dir {
SwipeDirection::Up if !self.is_final_page() => { SwipeDirection::Up if !self.is_final_page() => {
self.progress = 0; self.progress = 0;
self.page_index = (self.page_index + 1).min(self.share_words.len() as i16 - 1); self.page_index = (self.page_index + 1).min(self.share_words.len() as i16 - 1);
self.wait_for_attach = true;
ctx.request_paint(); ctx.request_paint();
} }
SwipeDirection::Down if !self.is_first_page() => { SwipeDirection::Down if !self.is_first_page() => {
self.progress = 0; self.progress = 0;
self.page_index = self.page_index.saturating_sub(1); self.page_index = self.page_index.saturating_sub(1);
self.wait_for_attach = true;
ctx.request_paint(); ctx.request_paint();
} }
_ => {} _ => {}
@ -260,35 +292,34 @@ impl<'a> Component for ShareWordsInner<'a> {
.with_fg(theme::GREY) .with_fg(theme::GREY)
.render(target); .render(target);
let (dir, should_animate) = if self.page_index < self.next_index { let (dir, should_animate_progress) = self.should_animate_progress();
(
SwipeDirection::Up,
self.page_index < self.share_words.len() as i16 - 1,
)
} else {
(SwipeDirection::Down, self.page_index > 0)
};
if self.progress > 0 && should_animate { if self.progress > 0 && should_animate_progress {
target.in_clip(self.area_word, &|target| { target.in_clip(self.area_word, &|target| {
let progress = pareen::constant(0.0).seq_ease_out( let bounds = target.viewport().clip;
0.0, let full_offset = dir.as_offset(bounds.size());
easer::functions::Cubic, let current_offset = full_offset * (self.progress as f32 / 1000.0);
1.0,
pareen::constant(1.0),
);
util::render_slide( target.with_origin(current_offset, &|target| {
|target| self.render_word(self.page_index, target, target.viewport().clip), self.render_word(self.page_index, target, target.viewport().clip)
|target| self.render_word(self.next_index, target, target.viewport().clip), });
progress.eval(self.progress as f32 / 1000.0), });
dir, } else if (self.attach_animation.is_active() || self.wait_for_attach) && self.progress == 0
target, {
) let t = self.attach_animation.eval();
let offset = self
.attach_animation
.get_offset(t, ShareWordsInner::AREA_WORD_HEIGHT);
target.in_clip(self.area_word, &|target| {
target.with_origin(offset, &|target| {
self.render_word(self.page_index, target, target.viewport().clip)
});
}); });
} else { } else {
self.render_word(self.page_index, target, self.area_word); self.render_word(self.page_index, target, self.area_word);
}; }
} }
} }

View File

@ -14,13 +14,13 @@ use crate::{
}; };
#[derive(Default, Clone)] #[derive(Default, Clone)]
struct AttachAnimation { pub struct SwipeAttachAnimation {
pub timer: Stopwatch, pub timer: Stopwatch,
pub attach_type: Option<AttachType>, pub attach_type: Option<AttachType>,
pub show_attach_anim: bool, pub show_attach_anim: bool,
} }
impl AttachAnimation { impl SwipeAttachAnimation {
const DURATION_MS: u32 = 500; const DURATION_MS: u32 = 500;
pub fn new() -> Self { pub fn new() -> Self {
@ -48,7 +48,7 @@ impl AttachAnimation {
self.timer.elapsed().to_millis() as f32 / 1000.0 self.timer.elapsed().to_millis() as f32 / 1000.0
} }
pub fn get_offset(&self, t: f32) -> Offset { pub fn get_offset(&self, t: f32, max_offset: i16) -> Offset {
let value = pareen::constant(0.0).seq_ease_in( let value = pareen::constant(0.0).seq_ease_in(
0.0, 0.0,
easer::functions::Linear, easer::functions::Linear,
@ -58,14 +58,14 @@ impl AttachAnimation {
match self.attach_type { match self.attach_type {
Some(AttachType::Initial) => { Some(AttachType::Initial) => {
Offset::lerp(Offset::new(0, -20), Offset::zero(), value.eval(t)) Offset::lerp(Offset::new(0, -max_offset), Offset::zero(), value.eval(t))
} }
Some(AttachType::Swipe(dir)) => match dir { Some(AttachType::Swipe(dir)) => match dir {
SwipeDirection::Up => { SwipeDirection::Up => {
Offset::lerp(Offset::new(0, 20), Offset::zero(), value.eval(t)) Offset::lerp(Offset::new(0, max_offset), Offset::zero(), value.eval(t))
} }
SwipeDirection::Down => { SwipeDirection::Down => {
Offset::lerp(Offset::new(0, -20), Offset::zero(), value.eval(t)) Offset::lerp(Offset::new(0, -max_offset), Offset::zero(), value.eval(t))
} }
_ => Offset::zero(), _ => Offset::zero(),
}, },
@ -130,7 +130,7 @@ impl AttachAnimation {
struct SwipeContext { struct SwipeContext {
progress: i16, progress: i16,
dir: SwipeDirection, dir: SwipeDirection,
attach_animation: AttachAnimation, attach_animation: SwipeAttachAnimation,
} }
impl SwipeContext { impl SwipeContext {
@ -138,7 +138,7 @@ impl SwipeContext {
Self { Self {
progress: 0, progress: 0,
dir: SwipeDirection::Up, dir: SwipeDirection::Up,
attach_animation: AttachAnimation::new(), attach_animation: SwipeAttachAnimation::new(),
} }
} }
@ -174,7 +174,7 @@ impl SwipeContext {
} else { } else {
let t = self.attach_animation.eval(); let t = self.attach_animation.eval();
let opacity = self.attach_animation.get_opacity(t); let opacity = self.attach_animation.get_opacity(t);
offset = self.attach_animation.get_offset(t); offset = self.attach_animation.get_offset(t, 20);
mask = 255 - opacity; mask = 255 - opacity;
if offset.x == 0 && offset.y == 0 { if offset.x == 0 && offset.y == 0 {

View File

@ -1,6 +1,5 @@
use crate::{ use crate::{
strutil::{ShortString, TString}, strutil::{ShortString, TString},
time::Duration,
ui::{ ui::{
component::text::TextStyle, component::text::TextStyle,
display, display,
@ -191,32 +190,6 @@ macro_rules! include_res {
} }
pub(crate) use include_res; pub(crate) use include_res;
pub const SLIDE_DURATION_MS: Duration = Duration::from_millis(333);
#[cfg(feature = "new_rendering")]
pub fn render_slide<'s, F0, F1, R>(
render_old: F0,
render_new: F1,
progress: f32,
direction: crate::ui::component::SwipeDirection,
target: &mut R,
) where
R: crate::ui::shape::Renderer<'s>,
F0: Fn(&mut R),
F1: Fn(&mut R),
{
let bounds = target.viewport().clip;
let full_offset = direction.as_offset(bounds.size());
let current_offset = full_offset * progress;
target.with_origin(current_offset, &|target| {
render_old(target);
});
target.with_origin(current_offset - full_offset, &|target| {
render_new(target);
});
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::strutil; use crate::strutil;