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 { from: Option, to: Option, duration: Duration } pub trait AnimationActor { fn get_value(&self) -> T; fn set_value(&mut self, value: T); } impl AnimationActor 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 for AnimDisplay<'a> { fn get_value(&self) -> Fract8 { self.0.brightness() } fn set_value(&mut self, opacity: Fract8) { self.0.set_brightness(opacity); } } impl AnimationActor for AnimatedSurface { 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 } 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 Animation { 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 { 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 { pub async fn apply(&self, actors: [&mut dyn AnimationActor; ACTOR_COUNT]) { let now = Instant::now(); trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}"); let mut actors = actors.into_iter(); let animators: [Slot; 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 { surface: S, is_on: bool, opacity: Fract8 } impl Deref for AnimatedSurface { type Target = S; fn deref(&self) -> &Self::Target { &self.surface } } impl DerefMut for AnimatedSurface { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.surface } } impl From for AnimatedSurface { fn from(surface: S) -> Self { trace!("create anim surface={surface:?}"); AnimatedSurface { surface, is_on: false, opacity: Fract8::MAX } } } impl AnimatedSurface { 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); } } } }