use std::collections::HashMap;
use bevy::prelude::*;
use bevy::ui::UiTransform;
use crossbeam_channel::Receiver;
mod apply;
mod eval;
pub(crate) mod props;
pub mod protocol;
mod runner;
use apply::apply_animated_nodes;
pub(crate) use apply::push_transform_dirt;
pub use eval::{Lerp, build_ui_transform};
use eval::{eval_color, eval_scalar};
pub use protocol::{
AnimatableProperty, AnimatedBindings, AnimationCommand, Binding, Driver, Easing, SharedId,
ValueKind,
};
pub use runner::{Runner, build_runner};
pub struct ReactUiAnimationsPlugin {
inbox: Receiver<AnimationCommand>,
}
impl ReactUiAnimationsPlugin {
pub fn new(inbox: Receiver<AnimationCommand>) -> Self {
Self { inbox }
}
}
impl Plugin for ReactUiAnimationsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SharedValues>()
.init_resource::<crate::layer::LayerContentDirt>()
.add_message::<AnimationSettled>()
.insert_resource(AnimationInbox(self.inbox.clone()))
.configure_sets(
Update,
(AnimationSet::Drain, AnimationSet::Tick, AnimationSet::Apply).chain(),
)
.add_systems(
Update,
(
drain_animation_commands.in_set(AnimationSet::Drain),
tick_animations.in_set(AnimationSet::Tick),
apply_animated_nodes.in_set(AnimationSet::Apply),
),
);
}
}
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub enum AnimationSet {
Drain,
Tick,
Apply,
}
#[derive(Component, Debug, Clone)]
#[require(UiTransform)]
pub struct AnimatedNode(pub AnimatedBindings);
#[derive(Message, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnimationSettled {
pub id: SharedId,
pub token: u64,
pub finished: bool,
}
#[derive(Resource)]
pub struct AnimationInbox(pub(crate) Receiver<AnimationCommand>);
#[derive(Resource, Default)]
pub struct SharedValues {
values: HashMap<SharedId, SharedValueState>,
settled: Vec<AnimationSettled>,
}
struct SharedValueState {
current: f32,
active: Option<Runner>,
token: Option<u64>,
}
impl SharedValueState {
fn interrupted(&mut self, id: SharedId) -> Option<AnimationSettled> {
self.active.as_ref()?;
let token = self.token.take()?;
Some(AnimationSettled {
id,
token,
finished: false,
})
}
}
impl SharedValues {
pub fn get(&self, id: SharedId) -> Option<f32> {
self.values.get(&id).map(|s| s.current)
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
fn declare(&mut self, id: SharedId, initial: f32) {
self.values.entry(id).or_insert(SharedValueState {
current: initial,
active: None,
token: None,
});
}
fn set(&mut self, id: SharedId, value: f32) {
let s = self.values.entry(id).or_insert(SharedValueState {
current: value,
active: None,
token: None,
});
self.settled.extend(s.interrupted(id));
s.current = value;
s.active = None;
}
fn animate(&mut self, id: SharedId, driver: &Driver, token: Option<u64>) {
let s = self.values.entry(id).or_insert(SharedValueState {
current: 0.0,
active: None,
token: None,
});
self.settled.extend(s.interrupted(id));
let from = s.current;
s.active = Some(build_runner(driver, from));
s.token = token;
}
fn cancel(&mut self, id: SharedId) {
if let Some(s) = self.values.get_mut(&id) {
self.settled.extend(s.interrupted(id));
s.active = None;
}
}
fn clear(&mut self) {
self.values.clear();
self.settled.clear();
}
fn tick(&mut self, dt: f32) {
for (&id, s) in self.values.iter_mut() {
if let Some(runner) = s.active.as_mut() {
let (value, finished) = runner.step(dt);
s.current = value;
if finished {
s.active = None;
if let Some(token) = s.token.take() {
self.settled.push(AnimationSettled {
id,
token,
finished: true,
});
}
}
}
}
}
fn take_settled(&mut self) -> Vec<AnimationSettled> {
std::mem::take(&mut self.settled)
}
}
fn drain_animation_commands(
inbox: Res<AnimationInbox>,
mut values: ResMut<SharedValues>,
mut settled: MessageWriter<AnimationSettled>,
) {
while let Ok(cmd) = inbox.0.try_recv() {
match cmd {
AnimationCommand::Declare { id, initial } => values.declare(id, initial),
AnimationCommand::Set { id, value } => values.set(id, value),
AnimationCommand::Animate { id, driver, token } => values.animate(id, &driver, token),
AnimationCommand::Cancel { id } => values.cancel(id),
AnimationCommand::Clear => values.clear(),
}
}
settled.write_batch(values.take_settled());
}
fn tick_animations(
time: Res<Time>,
mut values: ResMut<SharedValues>,
mut settled: MessageWriter<AnimationSettled>,
) {
values.tick(time.delta_secs());
settled.write_batch(values.take_settled());
}
#[cfg(test)]
mod tests;