use std::any::Any;
use crate::animators::{self, FrameTick};
use crate::control::{Control, Has, part_mut};
use crate::props;
use crate::tree::{Build, ControlId, Cx, Handle, Tree, wrong_state};
use crate::types::Dirty;
props!(FramesProps, FramesBuild, FramesSet {
auto_play / set_auto_play: bool = true, APPLY;
repeat / set_repeat: i32 = 0, NONE;
speed_ratio / set_speed_ratio: f32 = 1.0, APPLY;
default_frame / set_default_frame: i32 = 0, DRAW_APPLY;
});
type Handler = Box<dyn FnMut(&mut dyn Any, &mut Cx<'_>)>;
#[derive(Default)]
pub struct FramePlayer {
pub p: FramesProps,
ends: Vec<f32>,
current: usize,
position: f32,
playing: bool,
started_ms: Option<f64>,
repeats_left: i32,
play_when_available: bool,
placed: bool,
ticking: bool,
stopped: bool,
auto_play_off: bool,
default_applied: i32,
id: Option<ControlId>,
on_started: Option<Handler>,
on_finished: Option<Handler>,
}
impl FramePlayer {
pub fn current(&self) -> usize {
self.current
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn range(&self) -> f32 {
self.ends.last().copied().unwrap_or(0.0)
}
pub fn position(&self) -> f32 {
self.position
}
pub(crate) fn set_frames(&mut self, durations: impl IntoIterator<Item = f32>) {
let mut end = 0.0;
self.ends = durations
.into_iter()
.map(|duration| {
end += duration;
end
})
.collect();
self.seek(self.p.default_frame as f32);
}
fn run_ms(&self) -> f32 {
let (length, ratio) = (self.range(), self.p.speed_ratio);
if ratio < 1.0 { length * (1.0 + ratio) } else { length / ratio }
}
fn frame_at(&self, position: f32) -> usize {
let range = self.range();
if self.ends.is_empty() || range <= 0.0 {
return 0;
}
let mut position = if position < 0.0 { range + position } else { position };
position %= range;
self.ends.iter().position(|end| position < *end).unwrap_or(0)
}
fn next_change_ms(&self) -> Option<f64> {
let (began, range) = (self.started_ms?, self.range());
let end = *self.ends.get(self.current)?;
(self.playing && range > 0.0).then(|| began + (end / range * self.run_ms()) as f64)
}
fn seek(&mut self, position: f32) -> bool {
self.position = position;
let frame = self.frame_at(position);
std::mem::replace(&mut self.current, frame) != frame
}
fn advance(&mut self, time_ms: f64) -> (bool, bool, bool) {
if !self.playing {
return (false, false, false);
}
let started = self.started_ms.is_none();
let began = *self.started_ms.get_or_insert(time_ms);
let run_ms = self.run_ms();
let progress = if run_ms > 0.0 { ((time_ms - began) / run_ms as f64) as f32 } else { 1.0 };
if progress < 1.0 {
return (self.seek(progress * self.range()), started, false);
}
let changed = self.seek(self.range());
if self.repeats_left != 0 {
self.repeats_left -= (self.repeats_left > 0) as i32;
self.started_ms = Some(time_ms);
return (changed, started, false);
}
self.playing = false;
(changed, started, true)
}
}
impl Has<FramesProps> for FramePlayer {
fn part(&self) -> &FramesProps {
&self.p
}
fn part_mut(&mut self) -> &mut FramesProps {
&mut self.p
}
}
impl Control for FramePlayer {
fn on_props_changed(&mut self, cx: &mut Cx) {
if !self.p.auto_play && !std::mem::replace(&mut self.auto_play_off, true) {
self.stop();
if let Some(id) = self.id {
animators::sleep(cx.tree, id, 0.0);
}
}
self.auto_play_off &= !self.p.auto_play;
let default = self.p.default_frame;
if std::mem::replace(&mut self.default_applied, default) != default && !self.playing {
self.seek(default as f32);
}
}
}
impl FramePlayer {
fn stop(&mut self) {
self.stopped |= self.playing;
(self.playing, self.play_when_available) = (false, false);
}
}
fn player<T: Control>(control: &mut T) -> &mut FramePlayer {
part_mut(control).expect("the control embeds a FramePlayer")
}
impl<T: Has<FramesProps>> Build<T> {
pub fn on_started<S: Any>(mut self, mut f: impl FnMut(Handle<T>, &mut S, &mut Cx<'_>) + 'static) -> Self {
let me = self.handle();
player(self.control_mut()).on_started = Some(Box::new(move |state, cx| {
f(me, state.downcast_mut::<S>().unwrap_or_else(|| wrong_state::<S>()), cx)
}));
self
}
pub fn on_finished<S: Any>(mut self, mut f: impl FnMut(Handle<T>, &mut S, &mut Cx<'_>) + 'static) -> Self {
let me = self.handle();
player(self.control_mut()).on_finished = Some(Box::new(move |state, cx| {
f(me, state.downcast_mut::<S>().unwrap_or_else(|| wrong_state::<S>()), cx)
}));
self
}
}
fn start(tree: &mut Tree, id: ControlId) {
let Some(mut me) = tree.find_mut::<FramePlayer>(id) else { return };
let Some((changed, tick_now)) = me.control_mut().begin() else { return };
if changed {
me.mark(Dirty::DRAW);
}
if tick_now {
animators::start_frame(tree, id, tick);
}
}
impl FramePlayer {
fn begin(&mut self) -> Option<(bool, bool)> {
if self.ends.is_empty() || !self.placed {
self.play_when_available = true;
return None;
}
(self.play_when_available, self.playing, self.started_ms) = (false, true, None);
self.repeats_left = self.p.repeat;
let changed = self.seek(0.0);
Some((changed, !std::mem::replace(&mut self.ticking, true)))
}
}
pub(crate) fn placed(player: &mut FramePlayer, tree: &mut Tree, id: ControlId) {
player.id = Some(id);
let first = !std::mem::replace(&mut player.placed, true);
if first && player.p.auto_play {
player.play_when_available = true;
}
if !player.play_when_available {
return;
}
let Some((changed, tick_now)) = player.begin() else { return };
if changed {
tree.invalidate(id, Dirty::DRAW);
}
if tick_now {
animators::start_frame(tree, id, tick);
}
}
fn tick(id: ControlId, time_ms: f64, state: &mut dyn Any, cx: &mut Cx<'_>) -> FrameTick {
let mut tick = FrameTick { keep: false, state_touched: false };
let Some(mut me) = cx.tree.find_mut::<FramePlayer>(id) else { return tick };
let player = me.control_mut();
let stopped = std::mem::take(&mut player.stopped);
let (changed, started, finished) = player.advance(time_ms);
let mut on_started = if started { player.on_started.take() } else { None };
let mut on_finished = if finished || stopped { player.on_finished.take() } else { None };
tick.keep = player.playing;
if !tick.keep {
player.ticking = false;
}
if changed {
me.mark(Dirty::DRAW);
}
if let Some(handler) = on_finished.as_mut().filter(|_| stopped) {
handler(state, cx);
}
if let Some(handler) = &mut on_started {
handler(state, cx);
}
if let Some(handler) = on_finished.as_mut().filter(|_| finished) {
handler(state, cx);
}
tick.state_touched = on_started.is_some() || on_finished.is_some();
let Some(mut me) = cx.tree.find_mut::<FramePlayer>(id) else { return tick };
let player = me.control_mut();
if on_started.is_some() {
player.on_started = on_started;
}
if on_finished.is_some() {
player.on_finished = on_finished;
}
if let Some(wake) = player.next_change_ms().filter(|wake| *wake > time_ms) {
animators::sleep(cx.tree, id, wake);
}
tick
}
impl Cx<'_> {
pub fn start_frames(&mut self, control: impl Into<ControlId>) {
let id = control.into();
if let Some(mut me) = self.tree.find_mut::<FramePlayer>(id) {
me.control_mut().stop();
}
start(self.tree, id);
animators::sleep(self.tree, id, 0.0);
}
pub fn stop_frames(&mut self, control: impl Into<ControlId>) {
let id = control.into();
if let Some(mut me) = self.tree.find_mut::<FramePlayer>(id) {
me.control_mut().stop();
}
animators::sleep(self.tree, id, 0.0);
}
pub fn seek_frames(&mut self, control: impl Into<ControlId>, position: f32) {
if let Some(mut me) = self.tree.find_mut::<FramePlayer>(control)
&& me.control_mut().seek(position)
{
me.mark(Dirty::DRAW);
}
}
}