use std::any::Any;
use crate::Animation;
use crate::mem;
pub fn animate<T, R>(
ui: &mut egui::Ui,
id: impl Into<egui::Id>,
value: T,
animation: Animation,
add_contents: impl FnOnce(&mut egui::Ui, T) -> R,
) where
T: 'static + Any + Clone + Send + Sync + Default + PartialEq,
{
let id: egui::Id = id.into();
let current_time = ui.ctx().input(|input| input.time);
let current_value = value;
let start_value = mem::get_or_insert_start_value(ui, id, current_value.clone());
match start_value == current_value {
true => add_contents(ui, current_value),
false => {
let start_time = mem::get_or_insert_start_time(ui, id, current_time);
let animation = AnimationState::new(start_time, current_time, animation);
ui.ctx().request_repaint();
animation.animate(ui, id, start_value, current_value, add_contents)
}
};
}
pub fn run_state(ui: &mut egui::Ui, id: impl Into<egui::Id>, animation: Animation) -> RunState {
let id: egui::Id = id.into();
match mem::get_start_time(ui, id) {
Some(start_time) => {
let current_time = ui.ctx().input(|input| input.time);
AnimationState::new(start_time, current_time, animation).run_state()
}
None => Default::default(),
}
}
struct AnimationState {
start_time: f64,
current_time: f64,
animation: Animation,
}
impl AnimationState {
pub const fn new(start_time: f64, current_time: f64, animation: Animation) -> Self {
Self {
start_time,
current_time,
animation,
}
}
#[inline]
fn out_dur(&self) -> f32 {
self.animation.out_seg.duration
}
#[inline]
fn out_start(&self) -> f64 {
self.start_time
}
#[inline]
fn out_end(&self) -> f64 {
self.out_start() + self.out_dur() as f64
}
fn out_elapsed(&self) -> Option<f32> {
let out_elapsed = (self.current_time - self.out_start()).max(0.0) as f32;
(out_elapsed < self.out_dur()).then_some(out_elapsed)
}
fn out_elapsed_normal(&self) -> Option<f32> {
self.out_elapsed().map(|elapsed| elapsed / self.out_dur())
}
#[inline]
fn in_dur(&self) -> f32 {
self.animation.in_seg.duration
}
#[inline]
fn in_start(&self) -> f64 {
self.out_end()
}
#[allow(dead_code)]
#[inline]
fn in_end(&self) -> f64 {
self.in_start() + self.in_dur() as f64
}
fn in_elapsed(&self) -> Option<f32> {
let in_elapsed = (self.current_time - self.in_start()).max(0.0) as f32;
(in_elapsed < self.in_dur()).then_some(in_elapsed)
}
fn in_elapsed_normal(&self) -> Option<f32> {
self.in_elapsed().map(|elapsed| elapsed / self.in_dur())
}
fn animate<T: 'static + Any + Clone + Send + Sync + Default, R>(
&self,
ui: &mut egui::Ui,
id: egui::Id,
start_value: T,
current_value: T,
add_contents: impl FnOnce(&mut egui::Ui, T) -> R,
) -> R {
match self.run_state() {
RunState::OutSeg(normal) => {
self.animate_out(ui, id, normal, |ui| add_contents(ui, start_value))
}
RunState::InSeg(normal) => {
mem::clear_animation_layer(ui, id);
self.animate_in(ui, id, normal, |ui| add_contents(ui, current_value))
}
RunState::None => {
mem::clear_start_value::<T>(ui, id);
mem::clear_start_time(ui, id);
mem::clear_animation_layer(ui, id);
add_contents(ui, current_value)
}
}
}
#[inline]
fn animate_out<R>(
&self,
ui: &mut egui::Ui,
id: egui::Id,
normal: f32,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
self.animation.out_seg.animate(ui, id, normal, add_contents)
}
#[inline]
fn animate_in<R>(
&self,
ui: &mut egui::Ui,
id: egui::Id,
normal: f32,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
self.animation.in_seg.animate(ui, id, normal, add_contents)
}
fn run_state(&self) -> RunState {
if let Some(normal) = self.out_elapsed_normal() {
RunState::OutSeg(normal)
} else if let Some(normal) = self.in_elapsed_normal() {
RunState::InSeg(normal)
} else {
RunState::None
}
}
}
#[derive(Debug, Default, PartialEq, PartialOrd)]
pub enum RunState {
OutSeg(f32),
InSeg(f32),
#[default]
None,
}
impl RunState {
pub fn is_running(&self) -> bool {
match self {
RunState::OutSeg(_) | RunState::InSeg(_) => true,
RunState::None => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod animation_state {
use super::*;
const TEST_ANIM_STATE: AnimationState = AnimationState::new(
1.0,
1.0,
Animation {
out_seg: crate::AnimationSegment {
duration: 1.5,
anim_fn: |_, _| {},
},
in_seg: crate::AnimationSegment {
duration: 1.5,
anim_fn: |_, _| {},
},
},
);
#[test]
fn test_out_end() {
let state = TEST_ANIM_STATE;
assert_eq!(state.out_end(), 2.5);
}
#[test]
fn test_out_elapsed() {
let mut state = TEST_ANIM_STATE;
assert_eq!(state.out_elapsed(), Some(0.0));
state.current_time = 2.0;
assert_eq!(state.out_elapsed(), Some(1.0));
state.current_time = 3.0;
assert_eq!(state.out_elapsed(), None);
state.current_time = 4.0;
assert_eq!(state.out_elapsed(), None);
}
#[test]
fn test_out_elapsed_normal() {
let mut state = TEST_ANIM_STATE;
assert_eq!(state.out_elapsed_normal(), Some(0.0));
state.current_time = 1.75;
assert_eq!(state.out_elapsed_normal(), Some(0.5));
state.current_time = 3.0;
assert_eq!(state.out_elapsed_normal(), None);
state.current_time = 4.0;
assert_eq!(state.out_elapsed_normal(), None);
}
#[test]
fn test_in_end() {
let state = TEST_ANIM_STATE;
assert_eq!(state.in_end(), 4.0);
}
#[test]
fn test_in_elapsed() {
let mut state = TEST_ANIM_STATE;
assert_eq!(state.in_elapsed(), Some(0.0));
state.current_time = 2.0;
assert_eq!(state.in_elapsed(), Some(0.0));
state.current_time = 3.0;
assert_eq!(state.in_elapsed(), Some(0.5));
state.current_time = 4.0;
assert_eq!(state.in_elapsed(), None);
state.current_time = 5.0;
assert_eq!(state.in_elapsed(), None);
}
#[test]
fn test_in_elapsed_normal() {
let mut state = TEST_ANIM_STATE;
assert_eq!(state.in_elapsed_normal(), Some(0.0));
state.current_time = 2.0;
assert_eq!(state.in_elapsed_normal(), Some(0.0));
state.current_time = 3.25;
assert_eq!(state.in_elapsed_normal(), Some(0.5));
state.current_time = 4.0;
assert_eq!(state.in_elapsed_normal(), None);
state.current_time = 5.0;
assert_eq!(state.in_elapsed_normal(), None);
}
}
}