animation: move a few more steps towards a real generic animation framework
This commit is contained in:
201
src/animation.rs
201
src/animation.rs
@@ -1,8 +1,7 @@
|
|||||||
use embassy_time::{Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
use esp_rtos::CurrentThreadHandle;
|
|
||||||
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
|
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
|
||||||
use figments_render::output::Brightness;
|
use figments_render::output::Brightness;
|
||||||
use core::{fmt::Debug, mem::MaybeUninit, ops::{Deref, DerefMut}};
|
use core::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::graphics::display::DisplayControls;
|
use crate::graphics::display::DisplayControls;
|
||||||
@@ -53,6 +52,10 @@ impl<S: Surface> AnimationActor<Fract8> for AnimatedSurface<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait Tickable {
|
||||||
|
fn tick(&mut self) -> TickResult;
|
||||||
|
}
|
||||||
|
|
||||||
struct Slot<'a, T> {
|
struct Slot<'a, T> {
|
||||||
from: T,
|
from: T,
|
||||||
to: T,
|
to: T,
|
||||||
@@ -62,6 +65,38 @@ struct Slot<'a, T> {
|
|||||||
target: &'a mut dyn AnimationActor<T>
|
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> {
|
impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
f.debug_struct("Slot")
|
f.debug_struct("Slot")
|
||||||
@@ -73,7 +108,14 @@ impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animation<Fract8> {
|
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> Animation<T> {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
from: None,
|
from: None,
|
||||||
@@ -82,93 +124,21 @@ impl Animation<Fract8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn from(self, from: Fract8) -> Self {
|
async fn execute<'a, const ACTOR_COUNT: usize>(mut animators: [Slot<'a, T>; ACTOR_COUNT]) where Slot<'a, T>: Tickable {
|
||||||
Self {
|
let mut now: Instant = Instant::now();
|
||||||
from: Some(from),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn to(self, to: Fract8) -> Self {
|
|
||||||
Self {
|
|
||||||
to: Some(to),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn duration(self, duration: Duration) -> Self {
|
|
||||||
Self {
|
|
||||||
duration,
|
|
||||||
..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 mut now = Instant::now();
|
|
||||||
trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");
|
|
||||||
|
|
||||||
let mut actors = actors.into_iter();
|
|
||||||
let mut 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 steps = to.abs_diff(from);
|
|
||||||
|
|
||||||
let step_time = if steps == Fract8::MIN {
|
|
||||||
// Zero ticks is an 'invalid' animator that shouldn't get processed because start == end already
|
|
||||||
Duration::from_ticks(0)
|
|
||||||
} else {
|
|
||||||
// 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:?}");
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Find the next shortest delay
|
// Find the next shortest delay
|
||||||
let mut next_keyframe_time = animators[0].next_update;
|
let mut next_keyframe_time = animators[0].next_update;
|
||||||
let mut finished = false;
|
let mut finished = false;
|
||||||
for animator in &mut animators {
|
for animator in &mut animators {
|
||||||
if animator.step_time.as_ticks() == 0 {
|
if !animator.is_valid() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if animator.next_update <= now {
|
if animator.next_update <= now {
|
||||||
animator.next_update += animator.step_time;
|
finished = match animator.tick() {
|
||||||
animator.cur_step = if animator.to > animator.from {
|
TickResult::Finished => true,
|
||||||
animator.cur_step + Fract8::from_raw(1)
|
TickResult::Continue => finished
|
||||||
} else {
|
|
||||||
animator.cur_step - Fract8::from_raw(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (animator.to > animator.from && animator.cur_step >= animator.to) ||
|
|
||||||
(animator.to <= animator.from && animator.cur_step <= animator.to) {
|
|
||||||
finished = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animator.target.set_value(animator.cur_step);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
|
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
|
||||||
@@ -185,8 +155,75 @@ impl Animation<Fract8> {
|
|||||||
Timer::after(keyframe_delay).await;
|
Timer::after(keyframe_delay).await;
|
||||||
now += keyframe_delay;
|
now += keyframe_delay;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trace!("finished animators={animators:?}");
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user