1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-23 14:58:09 +00:00

feat(core): animate status screen in mercury ui

[no changelog]
This commit is contained in:
tychovrahe 2024-05-09 10:47:14 +02:00 committed by TychoVrahe
parent 47a2661736
commit 17072faa65

View File

@ -1,13 +1,87 @@
use crate::ui::{ use crate::{
time::{Duration, Stopwatch},
ui::{
component::{Component, Event, EventCtx, Swipe, SwipeDirection, Timeout}, component::{Component, Event, EventCtx, Swipe, SwipeDirection, Timeout},
constant::screen,
display::{Color, Icon}, display::{Color, Icon},
geometry::{Alignment2D, Rect}, geometry::{Alignment2D, Insets, Rect},
lerp::Lerp,
shape, shape,
shape::Renderer, shape::Renderer,
util::animation_disabled,
},
}; };
use super::theme; use super::theme;
const TIMEOUT_MS: u32 = 2000;
#[derive(Default, Clone)]
struct StatusAnimation {
pub timer: Stopwatch,
}
impl StatusAnimation {
pub fn is_active(&self) -> bool {
self.timer
.is_running_within(Duration::from_millis(TIMEOUT_MS))
}
pub fn eval(&self) -> f32 {
if animation_disabled() {
return 1.0;
}
self.timer.elapsed().to_millis() as f32 / 1000.0
}
pub fn get_instruction_opacity(&self, t: f32) -> u8 {
let instruction_opacity = pareen::constant(0.0).seq_ease_in_out(
0.0,
easer::functions::Cubic,
0.42,
pareen::constant(1.0),
);
u8::lerp(0, 255, instruction_opacity.eval(t))
}
pub fn get_content_opacity(&self, t: f32) -> u8 {
let content_opacity = pareen::constant(0.0).seq_ease_in_out(
0.18,
easer::functions::Cubic,
0.2,
pareen::constant(1.0),
);
u8::lerp(0, 255, content_opacity.eval(t))
}
pub fn get_circle_radius(&self, t: f32) -> i16 {
let circle_scale = pareen::constant(0.0).seq_ease_out(
0.2,
easer::functions::Cubic,
0.4,
pareen::constant(1.0),
);
const CIRCLE_DIAMETER_MAX: i16 = 170;
const CIRCLE_DIAMETER_MIN: i16 = 80;
i16::lerp(
CIRCLE_DIAMETER_MAX / 2,
CIRCLE_DIAMETER_MIN / 2,
circle_scale.eval(t),
)
}
pub fn start(&mut self) {
self.timer.start();
}
pub fn reset(&mut self) {
self.timer = Stopwatch::new_stopped();
}
}
/// Component showing status of an operation. Most typically embedded as a /// Component showing status of an operation. Most typically embedded as a
/// content of a Frame and showing success (checkmark with a circle around). /// content of a Frame and showing success (checkmark with a circle around).
#[derive(Clone)] #[derive(Clone)]
@ -17,6 +91,7 @@ pub struct StatusScreen {
icon_color: Color, icon_color: Color,
circle_color: Color, circle_color: Color,
dismiss_type: DismissType, dismiss_type: DismissType,
anim: StatusAnimation,
} }
#[derive(Clone)] #[derive(Clone)]
@ -25,8 +100,6 @@ enum DismissType {
Timeout(Timeout), Timeout(Timeout),
} }
const TIMEOUT_MS: u32 = 2000;
impl StatusScreen { impl StatusScreen {
fn new(icon: Icon, icon_color: Color, circle_color: Color, dismiss_style: DismissType) -> Self { fn new(icon: Icon, icon_color: Color, circle_color: Color, dismiss_style: DismissType) -> Self {
Self { Self {
@ -35,6 +108,7 @@ impl StatusScreen {
icon_color, icon_color,
circle_color, circle_color,
dismiss_type: dismiss_style, dismiss_type: dismiss_style,
anim: StatusAnimation::default(),
} }
} }
@ -87,6 +161,16 @@ impl Component for StatusScreen {
} }
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> { fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Attach = event {
self.anim.start();
ctx.request_paint();
ctx.request_anim_frame();
}
if self.anim.is_active() {
ctx.request_anim_frame();
ctx.request_paint();
}
match self.dismiss_type { match self.dismiss_type {
DismissType::SwipeUp(ref mut swipe) => { DismissType::SwipeUp(ref mut swipe) => {
let swipe_dir = swipe.event(ctx, event); let swipe_dir = swipe.event(ctx, event);
@ -109,7 +193,9 @@ impl Component for StatusScreen {
} }
fn render<'s>(&self, target: &mut impl Renderer<'s>) { fn render<'s>(&self, target: &mut impl Renderer<'s>) {
shape::Circle::new(self.area.center(), 40) let t = self.anim.eval();
shape::Circle::new(self.area.center(), self.anim.get_circle_radius(t))
.with_fg(self.circle_color) .with_fg(self.circle_color)
.with_bg(theme::BLACK) .with_bg(theme::BLACK)
.with_thickness(2) .with_thickness(2)
@ -118,6 +204,20 @@ impl Component for StatusScreen {
.with_align(Alignment2D::CENTER) .with_align(Alignment2D::CENTER)
.with_fg(self.icon_color) .with_fg(self.icon_color)
.render(target); .render(target);
//content + header cover
shape::Bar::new(self.area.outset(Insets::top(self.area.y0)))
.with_fg(theme::BLACK)
.with_bg(theme::BLACK)
.with_alpha(255 - self.anim.get_content_opacity(t))
.render(target);
//instruction cover
shape::Bar::new(screen().inset(Insets::top(self.area.y1)))
.with_fg(theme::BLACK)
.with_bg(theme::BLACK)
.with_alpha(255 - self.anim.get_instruction_opacity(t))
.render(target);
} }
} }