mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 22:38:08 +00:00
feat(core): animate status screen in mercury ui
[no changelog]
This commit is contained in:
parent
47a2661736
commit
17072faa65
@ -1,13 +1,87 @@
|
||||
use crate::ui::{
|
||||
use crate::{
|
||||
time::{Duration, Stopwatch},
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Swipe, SwipeDirection, Timeout},
|
||||
constant::screen,
|
||||
display::{Color, Icon},
|
||||
geometry::{Alignment2D, Rect},
|
||||
geometry::{Alignment2D, Insets, Rect},
|
||||
lerp::Lerp,
|
||||
shape,
|
||||
shape::Renderer,
|
||||
util::animation_disabled,
|
||||
},
|
||||
};
|
||||
|
||||
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
|
||||
/// content of a Frame and showing success (checkmark with a circle around).
|
||||
#[derive(Clone)]
|
||||
@ -17,6 +91,7 @@ pub struct StatusScreen {
|
||||
icon_color: Color,
|
||||
circle_color: Color,
|
||||
dismiss_type: DismissType,
|
||||
anim: StatusAnimation,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -25,8 +100,6 @@ enum DismissType {
|
||||
Timeout(Timeout),
|
||||
}
|
||||
|
||||
const TIMEOUT_MS: u32 = 2000;
|
||||
|
||||
impl StatusScreen {
|
||||
fn new(icon: Icon, icon_color: Color, circle_color: Color, dismiss_style: DismissType) -> Self {
|
||||
Self {
|
||||
@ -35,6 +108,7 @@ impl StatusScreen {
|
||||
icon_color,
|
||||
circle_color,
|
||||
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> {
|
||||
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 {
|
||||
DismissType::SwipeUp(ref mut swipe) => {
|
||||
let swipe_dir = swipe.event(ctx, event);
|
||||
@ -109,7 +193,9 @@ impl Component for StatusScreen {
|
||||
}
|
||||
|
||||
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_bg(theme::BLACK)
|
||||
.with_thickness(2)
|
||||
@ -118,6 +204,20 @@ impl Component for StatusScreen {
|
||||
.with_align(Alignment2D::CENTER)
|
||||
.with_fg(self.icon_color)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user