283 lines
7.8 KiB
Rust
283 lines
7.8 KiB
Rust
use embassy_time::{Duration, Instant, Timer};
|
|
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
|
|
use figments_render::output::Brightness;
|
|
use core::{fmt::Debug, ops::{Deref, DerefMut}};
|
|
use log::*;
|
|
|
|
use crate::graphics::display::DisplayControls;
|
|
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
pub struct Animation<T> {
|
|
from: Option<T>,
|
|
to: Option<T>,
|
|
duration: Duration
|
|
}
|
|
|
|
pub trait AnimationActor<T> {
|
|
fn get_value(&self) -> T;
|
|
fn set_value(&mut self, value: T);
|
|
}
|
|
|
|
impl<S: Surface> AnimationActor<Fract8> for S {
|
|
fn get_value(&self) -> Fract8 {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn set_value(&mut self, opacity: Fract8) {
|
|
self.set_opacity(opacity);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AnimDisplay<'a>(pub &'a mut DisplayControls);
|
|
|
|
impl<'a> AnimationActor<Fract8> for AnimDisplay<'a> {
|
|
fn get_value(&self) -> Fract8 {
|
|
self.0.brightness()
|
|
}
|
|
|
|
fn set_value(&mut self, opacity: Fract8) {
|
|
self.0.set_brightness(opacity);
|
|
}
|
|
}
|
|
|
|
impl<S: Surface> AnimationActor<Fract8> for AnimatedSurface<S> {
|
|
fn get_value(&self) -> Fract8 {
|
|
self.opacity
|
|
}
|
|
|
|
fn set_value(&mut self, opacity: Fract8) {
|
|
self.surface.set_opacity(opacity);
|
|
self.opacity = opacity;
|
|
}
|
|
}
|
|
|
|
trait Tickable {
|
|
fn tick(&mut self) -> TickResult;
|
|
}
|
|
|
|
struct Slot<'a, T> {
|
|
from: T,
|
|
to: T,
|
|
cur_step: T,
|
|
step_time: Duration,
|
|
next_update: Instant,
|
|
target: &'a mut dyn AnimationActor<T>
|
|
}
|
|
|
|
enum TickResult {
|
|
Finished,
|
|
Continue
|
|
}
|
|
|
|
impl<'a, T> Slot<'a, T> {
|
|
fn is_valid(&self) -> bool {
|
|
self.step_time.as_ticks() != 0
|
|
}
|
|
}
|
|
|
|
impl<'a> Tickable for Slot<'a, Fract8> {
|
|
/// Advances the animation by one tick, and then returns whether or not the animation should continue or not
|
|
fn tick(&mut self) -> TickResult {
|
|
self.next_update += self.step_time;
|
|
self.cur_step = if self.to > self.from {
|
|
self.cur_step + Fract8::from_raw(1)
|
|
} else {
|
|
self.cur_step - Fract8::from_raw(1)
|
|
};
|
|
|
|
self.target.set_value(self.cur_step);
|
|
|
|
if (self.to > self.from && self.cur_step >= self.to) ||
|
|
(self.to <= self.from && self.cur_step <= self.to) {
|
|
TickResult::Finished
|
|
} else {
|
|
TickResult::Continue
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
f.debug_struct("Slot")
|
|
.field("from", &self.from)
|
|
.field("to", &self.to)
|
|
.field("cur_step", &self.cur_step)
|
|
.field("step_time", &self.step_time)
|
|
.field("next_update", &self.next_update).finish()
|
|
}
|
|
}
|
|
|
|
struct Animator<'a, T, const ACTOR_COUNT: usize> {
|
|
animators: [Slot<'a, T>; ACTOR_COUNT]
|
|
}
|
|
|
|
impl<'a, T, const ACTOR_COUNT: usize> Animator<'a, T, ACTOR_COUNT> {
|
|
}
|
|
|
|
impl<T: core::fmt::Debug> Animation<T> {
|
|
pub const fn new() -> Self {
|
|
Self {
|
|
from: None,
|
|
to: None,
|
|
duration: Duration::from_ticks(0)
|
|
}
|
|
}
|
|
|
|
async fn execute<'a, const ACTOR_COUNT: usize>(mut animators: [Slot<'a, T>; ACTOR_COUNT]) where Slot<'a, T>: Tickable {
|
|
let mut now: Instant = Instant::now();
|
|
loop {
|
|
// Find the next shortest delay
|
|
let mut next_keyframe_time = Instant::MAX;
|
|
let mut finished = false;
|
|
let mut has_valid = false;
|
|
for animator in &mut animators {
|
|
if !animator.is_valid() {
|
|
continue;
|
|
}
|
|
has_valid = true;
|
|
if animator.next_update <= now {
|
|
finished = match animator.tick() {
|
|
TickResult::Finished => true,
|
|
TickResult::Continue => finished
|
|
}
|
|
}
|
|
|
|
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
|
|
next_keyframe_time = animator.next_update;
|
|
}
|
|
}
|
|
|
|
// If there are no valid animators, or if all animators are finished, then we're done
|
|
finished |= !has_valid;
|
|
if finished {
|
|
break;
|
|
}
|
|
|
|
assert!(next_keyframe_time > now, "Weird times: {next_keyframe_time:?} is earlier than {now:?} animators={animators:?}");
|
|
let keyframe_delay = next_keyframe_time - now;
|
|
trace!("delay {:?}", keyframe_delay.as_millis());
|
|
Timer::after(keyframe_delay).await;
|
|
now += keyframe_delay;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Animation<Fract8> {
|
|
pub const fn duration(self, duration: Duration) -> Self {
|
|
Self {
|
|
duration,
|
|
..self
|
|
}
|
|
}
|
|
|
|
pub const fn from(self, from: Fract8) -> Self {
|
|
Self {
|
|
from: Some(from),
|
|
..self
|
|
}
|
|
}
|
|
|
|
pub const fn to(self, to: Fract8) -> Self {
|
|
Self {
|
|
to: Some(to),
|
|
..self
|
|
}
|
|
}
|
|
}
|
|
|
|
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
|
|
|
|
impl Animation<Fract8> {
|
|
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
|
|
|
|
let now = Instant::now();
|
|
trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");
|
|
|
|
let mut actors = actors.into_iter();
|
|
let animators: [Slot<Fract8>; ACTOR_COUNT] = core::array::from_fn(|_| {
|
|
let target = actors.next().unwrap();
|
|
let from = if let Some(val) = self.from {
|
|
val
|
|
} else {
|
|
target.get_value()
|
|
};
|
|
let to = if let Some(val) = self.to {
|
|
val
|
|
} else {
|
|
target.get_value()
|
|
};
|
|
|
|
let step_time = if to == from {
|
|
// Zero ticks is an 'invalid' animator that shouldn't get processed because start == end already
|
|
Duration::from_ticks(0)
|
|
} else {
|
|
let steps = to.abs_diff(from);
|
|
// FIXME: if the resulting duration is less than the animation rate, we also need to re-scale the number added to animator.cur_step further down below. Otherwise a 0-255 animation with a 100ms duration actually ends up running for 255ms
|
|
(self.duration / steps.to_raw().into()).max(MIN_ANIMATION_RATE)
|
|
};
|
|
Slot {
|
|
from,
|
|
to,
|
|
cur_step: from,
|
|
step_time,
|
|
next_update: now,
|
|
target
|
|
}
|
|
});
|
|
|
|
trace!("animators={animators:?}");
|
|
|
|
Self::execute(animators).await;
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AnimatedSurface<S: Surface> {
|
|
surface: S,
|
|
is_on: bool,
|
|
opacity: Fract8
|
|
}
|
|
|
|
impl<S: Surface> Deref for AnimatedSurface<S> {
|
|
type Target = S;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.surface
|
|
}
|
|
}
|
|
|
|
impl<S: Surface> DerefMut for AnimatedSurface<S> {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.surface
|
|
}
|
|
}
|
|
|
|
impl<S: Surface + Debug> From<S> for AnimatedSurface<S> {
|
|
fn from(surface: S) -> Self {
|
|
trace!("create anim surface={surface:?}");
|
|
AnimatedSurface {
|
|
surface,
|
|
is_on: false,
|
|
opacity: Fract8::MAX
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<S: Surface + Debug> AnimatedSurface<S> {
|
|
pub async fn set_on(&mut self, is_on: bool) {
|
|
if self.is_on != is_on {
|
|
let anim = if is_on {
|
|
self.surface.set_visible(true);
|
|
Animation::default().duration(Duration::from_secs(1)).from(Fract8::MIN).to(Fract8::MAX)
|
|
} else {
|
|
Animation::default().duration(Duration::from_secs(1)).from(Fract8::MAX).to(Fract8::MIN)
|
|
};
|
|
anim.apply([&mut self.surface]).await;
|
|
self.is_on = true;
|
|
if !is_on {
|
|
self.surface.set_visible(false);
|
|
}
|
|
}
|
|
}
|
|
} |