///|
/// Playback state for embedders that need deterministic frame stepping.
pub(all) enum PlaybackMode {
Once
Loop
PingPong
Clamp
} derive(Eq, Debug)
///|
pub(all) struct PlaybackState {
animation : Animation
mode : PlaybackMode
time_cs : Int
direction : Int
playing : Bool
} derive(Debug)
///|
pub fn PlaybackState::new(
animation : Animation,
mode : PlaybackMode,
) -> PlaybackState {
{ animation, mode, time_cs: 0, direction: 1, playing: true }
}
///|
pub fn PlaybackState::animation(self : PlaybackState) -> Animation {
self.animation
}
///|
pub fn PlaybackState::mode(self : PlaybackState) -> PlaybackMode {
self.mode
}
///|
pub fn PlaybackState::time(self : PlaybackState) -> Int {
self.time_cs
}
///|
pub fn PlaybackState::playing(self : PlaybackState) -> Bool {
self.playing
}
///|
pub fn PlaybackState::pause(self : PlaybackState) -> PlaybackState {
{ ..self, playing: false }
}
///|
pub fn PlaybackState::play(self : PlaybackState) -> PlaybackState {
{ ..self, playing: true }
}
///|
pub fn PlaybackState::seek(
self : PlaybackState,
time_cs : Int,
) -> PlaybackState {
{ ..self, time_cs: if time_cs < 0 { 0 } else { time_cs } }
}
///|
pub fn PlaybackState::reset(self : PlaybackState) -> PlaybackState {
{ ..self, time_cs: 0, direction: 1, playing: true }
}
///|
pub fn PlaybackState::current_frame(self : PlaybackState) -> Frame? {
self.animation.frame_at(self.time_cs)
}
///|
fn clamp_playback_time(animation : Animation, time_cs : Int) -> Int {
let duration = animation.duration_cs()
if duration <= 0 {
0
} else if time_cs < 0 {
0
} else if time_cs >= duration {
duration - 1
} else {
time_cs
}
}
///|
/// Advance playback by centiseconds, applying the selected mode at boundaries.
pub fn PlaybackState::advance(
self : PlaybackState,
elapsed_cs : Int,
) -> PlaybackState {
if !self.playing || self.animation.frame_count() == 0 {
self
} else {
let duration = self.animation.duration_cs()
let step = if elapsed_cs < 0 { 0 } else { elapsed_cs }
if duration <= 0 {
{ ..self, playing: false }
} else {
match self.mode {
Once => {
let next = self.time_cs + step
if next >= duration {
{ ..self, time_cs: duration - 1, playing: false }
} else {
{ ..self, time_cs: next }
}
}
Clamp =>
{
..self,
time_cs: clamp_playback_time(self.animation, self.time_cs + step),
}
Loop => { ..self, time_cs: (self.time_cs + step) % duration }
PingPong => {
let period = duration * 2
let phase = (self.time_cs + step) % period
let time = if phase < duration { phase } else { period - phase - 1 }
let direction = if phase < duration { 1 } else { -1 }
{ ..self, time_cs: time, direction }
}
}
}
}
}
///|
/// Advance by a frame count using the animation's own delays.
pub fn PlaybackState::step_frames(
self : PlaybackState,
count : Int,
) -> PlaybackState {
let mut result = self
let safe_count = if count < 0 { 0 } else { count }
for _ in 0.. result = result.advance(1)
Some(frame) => result = result.advance(frame.delay_cs())
}
}
result
}
///|
/// Return normalized playback progress in per mille.
pub fn PlaybackState::progress(self : PlaybackState) -> Int {
let duration = self.animation.duration_cs()
if duration <= 0 {
0
} else {
self.time_cs * 1000 / duration
}
}
///|
/// Build a list of visible frame indices at regular playback intervals.
pub fn sample_playback(state : PlaybackState, samples : Int) -> Array[Int] {
let result : Array[Int] = []
let count = if samples < 1 { 1 } else { samples }
let duration = state.animation.duration_cs()
for i in 0.. ()
Some(position) => result.push(position.index())
}
}
result
}
///|
/// Produce a playback snapshot suitable for a UI overlay.
pub(all) struct PlaybackSnapshot {
time_cs : Int
frame_index : Int
progress_per_mille : Int
playing : Bool
} derive(Debug)
///|
pub fn PlaybackSnapshot::time(self : PlaybackSnapshot) -> Int {
self.time_cs
}
///|
pub fn PlaybackSnapshot::frame_index(self : PlaybackSnapshot) -> Int {
self.frame_index
}
///|
pub fn PlaybackSnapshot::progress(self : PlaybackSnapshot) -> Int {
self.progress_per_mille
}
///|
pub fn PlaybackSnapshot::playing(self : PlaybackSnapshot) -> Bool {
self.playing
}
///|
pub fn PlaybackState::snapshot(self : PlaybackState) -> PlaybackSnapshot {
let frame_index = match self.animation.position_at(self.time_cs) {
None => -1
Some(position) => position.index()
}
PlaybackSnapshot::{
time_cs: self.time_cs,
frame_index,
progress_per_mille: self.progress(),
playing: self.playing,
}
}