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:
parent
008490bf4b
commit
a15dd6598f
1
core/.changelog.d/4063.changed
Normal file
1
core/.changelog.d/4063.changed
Normal file
@ -0,0 +1 @@
|
|||||||
|
[T3T1] Improve share words swiping animation
|
@ -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);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user