use crate::easing::Easing;
use slotmap::{new_key_type, SlotMap};
new_key_type! {
pub struct TimelineEntryId;
}
struct TimelineEntry {
offset_ms: i32,
duration_ms: u32,
start_value: f32,
end_value: f32,
easing: Easing,
}
pub struct Timeline {
entries: SlotMap<TimelineEntryId, TimelineEntry>,
current_time: f32,
duration_ms: u32,
playing: bool,
loop_count: i32, current_loop: i32,
alternate: bool,
reversed: bool,
playback_rate: f32,
}
impl Timeline {
pub fn new() -> Self {
Self {
entries: SlotMap::with_key(),
current_time: 0.0,
duration_ms: 0,
playing: false,
loop_count: 1,
current_loop: 0,
alternate: false,
reversed: false,
playback_rate: 1.0,
}
}
pub fn add(
&mut self,
offset_ms: i32,
duration_ms: u32,
start_value: f32,
end_value: f32,
) -> TimelineEntryId {
self.add_with_easing(
offset_ms,
duration_ms,
start_value,
end_value,
Easing::Linear,
)
}
pub fn add_with_easing(
&mut self,
offset_ms: i32,
duration_ms: u32,
start_value: f32,
end_value: f32,
easing: Easing,
) -> TimelineEntryId {
let id = self.entries.insert(TimelineEntry {
offset_ms,
duration_ms,
start_value,
end_value,
easing,
});
let end_time = (offset_ms.max(0) as u32) + duration_ms;
self.duration_ms = self.duration_ms.max(end_time);
id
}
pub fn start(&mut self) {
self.current_time = if self.reversed {
self.duration_ms as f32
} else {
0.0
};
self.current_loop = 0;
self.playing = true;
}
pub fn stop(&mut self) {
self.playing = false;
}
pub fn pause(&mut self) {
self.playing = false;
}
pub fn resume(&mut self) {
self.playing = true;
}
pub fn reverse(&mut self) {
self.reversed = !self.reversed;
}
pub fn seek(&mut self, time_ms: f32) {
self.current_time = time_ms.clamp(0.0, self.duration_ms as f32);
}
pub fn set_loop(&mut self, count: i32) {
self.loop_count = count;
}
pub fn set_alternate(&mut self, enabled: bool) {
self.alternate = enabled;
}
pub fn set_playback_rate(&mut self, rate: f32) {
self.playback_rate = rate.max(0.0);
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn is_reversed(&self) -> bool {
self.reversed
}
pub fn duration(&self) -> u32 {
self.duration_ms
}
pub fn current_time(&self) -> f32 {
self.current_time
}
pub fn current_loop(&self) -> i32 {
self.current_loop
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
pub fn entry_ids(&self) -> Vec<TimelineEntryId> {
self.entries.keys().collect()
}
pub fn tick(&mut self, dt_ms: f32) {
if !self.playing {
return;
}
let dt_adjusted = dt_ms * self.playback_rate;
if self.reversed {
self.current_time -= dt_adjusted;
if self.current_time <= 0.0 {
self.handle_boundary(0.0);
}
} else {
self.current_time += dt_adjusted;
if self.current_time >= self.duration_ms as f32 {
self.handle_boundary(self.duration_ms as f32);
}
}
}
fn handle_boundary(&mut self, boundary_time: f32) {
let should_loop = self.loop_count == -1 || self.current_loop < self.loop_count - 1;
if should_loop {
self.current_loop += 1;
if self.alternate {
self.reversed = !self.reversed;
self.current_time = boundary_time;
} else {
self.current_time = if self.reversed {
self.duration_ms as f32
} else {
0.0
};
}
} else {
self.current_time = boundary_time;
self.playing = false;
}
}
pub fn value(&self, id: TimelineEntryId) -> Option<f32> {
let entry = self.entries.get(id)?;
let local_time = self.current_time - entry.offset_ms as f32;
if local_time < 0.0 {
return Some(entry.start_value);
}
if local_time >= entry.duration_ms as f32 {
return Some(entry.end_value);
}
let progress = local_time / entry.duration_ms as f32;
let eased_progress = entry.easing.apply(progress);
Some(entry.start_value + (entry.end_value - entry.start_value) * eased_progress)
}
pub fn entry_progress(&self, id: TimelineEntryId) -> Option<f32> {
let entry = self.entries.get(id)?;
let local_time = self.current_time - entry.offset_ms as f32;
if local_time < 0.0 {
Some(0.0)
} else if local_time >= entry.duration_ms as f32 {
Some(1.0)
} else {
Some(local_time / entry.duration_ms as f32)
}
}
pub fn progress(&self) -> f32 {
if self.duration_ms == 0 {
return 1.0;
}
self.current_time / self.duration_ms as f32
}
}
impl Default for Timeline {
fn default() -> Self {
Self::new()
}
}
pub struct StaggerBuilder<'a> {
timeline: &'a mut Timeline,
base_offset: i32,
stagger: i32,
current_index: usize,
}
impl<'a> StaggerBuilder<'a> {
pub fn new(timeline: &'a mut Timeline, base_offset: i32, stagger: i32) -> Self {
Self {
timeline,
base_offset,
stagger,
current_index: 0,
}
}
pub fn add(&mut self, duration_ms: u32, start_value: f32, end_value: f32) -> TimelineEntryId {
self.add_with_easing(duration_ms, start_value, end_value, Easing::Linear)
}
pub fn add_with_easing(
&mut self,
duration_ms: u32,
start_value: f32,
end_value: f32,
easing: Easing,
) -> TimelineEntryId {
let offset = self.base_offset + (self.current_index as i32 * self.stagger);
self.current_index += 1;
self.timeline
.add_with_easing(offset, duration_ms, start_value, end_value, easing)
}
}
impl Timeline {
pub fn stagger(&mut self, base_offset: i32, stagger: i32) -> StaggerBuilder<'_> {
StaggerBuilder::new(self, base_offset, stagger)
}
}