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:
parent
47a2661736
commit
17072faa65
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user