|
|
|
@ -1,10 +1,12 @@
|
|
|
|
|
use super::theme;
|
|
|
|
|
use crate::{
|
|
|
|
|
strutil::TString,
|
|
|
|
|
time::{Duration, Stopwatch},
|
|
|
|
|
ui::{
|
|
|
|
|
component::{Component, Event, EventCtx, Timeout},
|
|
|
|
|
component::{Component, Event, EventCtx, Label, Timeout},
|
|
|
|
|
constant::screen,
|
|
|
|
|
display::{Color, Icon},
|
|
|
|
|
geometry::{Alignment2D, Insets, Rect},
|
|
|
|
|
geometry::{Alignment, Alignment2D, Insets, Point, Rect},
|
|
|
|
|
lerp::Lerp,
|
|
|
|
|
shape,
|
|
|
|
|
shape::Renderer,
|
|
|
|
@ -12,9 +14,8 @@ use crate::{
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use super::theme;
|
|
|
|
|
|
|
|
|
|
const TIMEOUT_MS: u32 = 2000;
|
|
|
|
|
const ANIMATION_TIME_MS: u32 = 1200;
|
|
|
|
|
const TIMEOUT_MS: u32 = ANIMATION_TIME_MS + 2000;
|
|
|
|
|
|
|
|
|
|
#[derive(Default, Clone)]
|
|
|
|
|
struct StatusAnimation {
|
|
|
|
@ -28,12 +29,12 @@ impl StatusAnimation {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.timer
|
|
|
|
|
.is_running_within(Duration::from_millis(TIMEOUT_MS))
|
|
|
|
|
.is_running_within(Duration::from_millis(ANIMATION_TIME_MS))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn eval(&self) -> f32 {
|
|
|
|
|
if animation_disabled() {
|
|
|
|
|
return TIMEOUT_MS as f32 / 1000.0;
|
|
|
|
|
return ANIMATION_TIME_MS as f32 / 1000.0;
|
|
|
|
|
}
|
|
|
|
|
self.timer.elapsed().to_millis() as f32 / 1000.0
|
|
|
|
|
}
|
|
|
|
@ -67,14 +68,54 @@ impl StatusAnimation {
|
|
|
|
|
pareen::constant(1.0),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let circle_scale2 = pareen::constant(0.0).seq_ease_out(
|
|
|
|
|
0.8,
|
|
|
|
|
easer::functions::Linear,
|
|
|
|
|
0.2,
|
|
|
|
|
pareen::constant(1.0),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const CIRCLE_DIAMETER_MAX: i16 = 170;
|
|
|
|
|
const CIRCLE_DIAMETER_MIN: i16 = 80;
|
|
|
|
|
|
|
|
|
|
i16::lerp(
|
|
|
|
|
let a = i16::lerp(
|
|
|
|
|
CIRCLE_DIAMETER_MAX / 2,
|
|
|
|
|
CIRCLE_DIAMETER_MIN / 2,
|
|
|
|
|
circle_scale.eval(t),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let b = i16::lerp(CIRCLE_DIAMETER_MIN / 2, 0, circle_scale2.eval(t));
|
|
|
|
|
|
|
|
|
|
if t > 0.8 {
|
|
|
|
|
b
|
|
|
|
|
} else {
|
|
|
|
|
a
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_circle_position(&self, t: f32, start: Point) -> Point {
|
|
|
|
|
let circle_pos = pareen::constant(0.0).seq_ease_in_out(
|
|
|
|
|
0.8,
|
|
|
|
|
easer::functions::Linear,
|
|
|
|
|
0.2,
|
|
|
|
|
pareen::constant(1.0),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const CIRCLE_DIAMETER_MAX: i16 = 170;
|
|
|
|
|
const CIRCLE_DIAMETER_MIN: i16 = 80;
|
|
|
|
|
|
|
|
|
|
Point::lerp(start, Point::zero(), circle_pos.eval(t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_content2_opacity(&self, t: f32) -> u8 {
|
|
|
|
|
let content_opacity = pareen::constant(0.0).seq_ease_in(
|
|
|
|
|
1.0,
|
|
|
|
|
easer::functions::Linear,
|
|
|
|
|
0.2,
|
|
|
|
|
pareen::constant(1.0),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
u8::lerp(0, 255, content_opacity.eval(t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn start(&mut self) {
|
|
|
|
@ -96,6 +137,7 @@ pub struct StatusScreen {
|
|
|
|
|
circle_color: Color,
|
|
|
|
|
dismiss_type: DismissType,
|
|
|
|
|
anim: StatusAnimation,
|
|
|
|
|
msg: Label<'static>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
@ -105,7 +147,13 @@ enum DismissType {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
msg: TString<'static>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
area: Rect::zero(),
|
|
|
|
|
icon,
|
|
|
|
@ -113,42 +161,47 @@ impl StatusScreen {
|
|
|
|
|
circle_color,
|
|
|
|
|
dismiss_type: dismiss_style,
|
|
|
|
|
anim: StatusAnimation::default(),
|
|
|
|
|
msg: Label::new(msg, Alignment::Start, theme::TEXT_NORMAL).vertically_centered(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_success() -> Self {
|
|
|
|
|
pub fn new_success(msg: TString<'static>) -> Self {
|
|
|
|
|
Self::new(
|
|
|
|
|
theme::ICON_SIMPLE_CHECKMARK,
|
|
|
|
|
theme::GREEN_LIME,
|
|
|
|
|
theme::GREEN_LIGHT,
|
|
|
|
|
DismissType::SwipeUp,
|
|
|
|
|
msg,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_success_timeout() -> Self {
|
|
|
|
|
pub fn new_success_timeout(msg: TString<'static>) -> Self {
|
|
|
|
|
Self::new(
|
|
|
|
|
theme::ICON_SIMPLE_CHECKMARK,
|
|
|
|
|
theme::GREEN_LIME,
|
|
|
|
|
theme::GREEN_LIGHT,
|
|
|
|
|
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),
|
|
|
|
|
msg,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_neutral() -> Self {
|
|
|
|
|
pub fn new_neutral(msg: TString<'static>) -> Self {
|
|
|
|
|
Self::new(
|
|
|
|
|
theme::ICON_SIMPLE_CHECKMARK,
|
|
|
|
|
theme::GREY_EXTRA_LIGHT,
|
|
|
|
|
theme::GREY_DARK,
|
|
|
|
|
DismissType::SwipeUp,
|
|
|
|
|
msg,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_neutral_timeout() -> Self {
|
|
|
|
|
pub fn new_neutral_timeout(msg: TString<'static>) -> Self {
|
|
|
|
|
Self::new(
|
|
|
|
|
theme::ICON_SIMPLE_CHECKMARK,
|
|
|
|
|
theme::GREY_EXTRA_LIGHT,
|
|
|
|
|
theme::GREY_DARK,
|
|
|
|
|
DismissType::Timeout(Timeout::new(TIMEOUT_MS)),
|
|
|
|
|
msg,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -158,6 +211,7 @@ impl Component for StatusScreen {
|
|
|
|
|
|
|
|
|
|
fn place(&mut self, bounds: Rect) -> Rect {
|
|
|
|
|
self.area = bounds;
|
|
|
|
|
self.msg.place(bounds);
|
|
|
|
|
bounds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -190,15 +244,25 @@ impl Component for StatusScreen {
|
|
|
|
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
|
|
|
|
let t = self.anim.eval();
|
|
|
|
|
|
|
|
|
|
shape::Circle::new(self.area.center(), self.anim.get_circle_radius(t))
|
|
|
|
|
let pos = self.anim.get_circle_position(t, self.area.center());
|
|
|
|
|
|
|
|
|
|
let content_2_alpha = self.anim.get_content2_opacity(t);
|
|
|
|
|
let content_1_alpha = 255 - content_2_alpha;
|
|
|
|
|
|
|
|
|
|
shape::Circle::new(pos, self.anim.get_circle_radius(t))
|
|
|
|
|
.with_fg(self.circle_color)
|
|
|
|
|
.with_bg(theme::BLACK)
|
|
|
|
|
.with_thickness(2)
|
|
|
|
|
.with_alpha(content_1_alpha)
|
|
|
|
|
.render(target);
|
|
|
|
|
shape::ToifImage::new(self.area.center(), self.icon.toif)
|
|
|
|
|
.with_align(Alignment2D::CENTER)
|
|
|
|
|
.with_fg(self.icon_color)
|
|
|
|
|
.render(target);
|
|
|
|
|
|
|
|
|
|
if self.anim.get_circle_radius(t) > 20 {
|
|
|
|
|
shape::ToifImage::new(pos, self.icon.toif)
|
|
|
|
|
.with_align(Alignment2D::CENTER)
|
|
|
|
|
.with_fg(self.icon_color)
|
|
|
|
|
.with_alpha(content_1_alpha)
|
|
|
|
|
.render(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//content + header cover
|
|
|
|
|
shape::Bar::new(self.area.outset(Insets::top(self.area.y0)))
|
|
|
|
@ -213,6 +277,8 @@ impl Component for StatusScreen {
|
|
|
|
|
.with_bg(theme::BLACK)
|
|
|
|
|
.with_alpha(255 - self.anim.get_instruction_opacity(t))
|
|
|
|
|
.render(target);
|
|
|
|
|
|
|
|
|
|
self.msg.render_with_alpha(target, content_2_alpha);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -220,5 +286,6 @@ impl Component for StatusScreen {
|
|
|
|
|
impl crate::trace::Trace for StatusScreen {
|
|
|
|
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
|
|
|
t.component("StatusScreen");
|
|
|
|
|
t.child("msg", &self.msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|