Skip to content
Snippets Groups Projects
systems.rs 4.15 KiB
Newer Older
Louis's avatar
Louis committed
use crate::components::{Tween, TweenEasing, TweenMode, TweenTarget, Tweenable, UpdateTween};
use crate::events::TweenLooped;
use crate::{
	DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT, DESPAWN_ON_TWEEN_COMPLETE_EVENT, TweenComplete,
	UpdateUserData,
};
use bevy_ecs::entity::Entity;
use bevy_ecs::event::{EventReader, EventWriter};
use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::query::With;
use bevy_ecs::relationship::Relationship;
use bevy_ecs::system::{Commands, In, Query, Res};
use bevy_time::Time;
use std::time::Duration;

pub fn despawn_on_complete(mut commands: Commands, mut events: EventReader<TweenComplete>) {
	for event in events.read() {
		if event.user_data == DESPAWN_ON_TWEEN_COMPLETE_EVENT {
			log::debug!("Despawn on complete {:?}", event.entity);
			commands.entity(event.entity).despawn();
		}
	}
}

pub fn despawn_ancestors_on_complete(
	mut commands: Commands,
	mut events: EventReader<TweenComplete>,
	parent_query: Query<&ChildOf>,
) {
	for event in events.read() {
		if event.user_data == DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT {
			let mut assumed_root = event.entity;
			while let Ok(rel) = parent_query.get(assumed_root) {
				assumed_root = rel.parent();
			}

			log::debug!(
				"Despawn on complete {:?} [Found parent {:?}]",
				event.entity,
				assumed_root
			);
			commands.entity(assumed_root).despawn();
		}
	}
}

pub fn tick_tweens<T: Tweenable>(
	mut commands: Commands,
	time: Res<Time>,
	mut tweeners: Query<(Entity, &mut Tween<T>, &TweenTarget, &TweenMode)>,
	mut components: Query<&mut T::Comp>,
	mut complete_events: EventWriter<TweenComplete>,
	mut looped_events: EventWriter<TweenLooped>,
) {
	let delta = time.delta();
	for (entity, mut tween, target, mode) in &mut tweeners {
		if !tween.delay.is_zero() {
			tween.delay = tween.delay.saturating_sub(delta);
			continue;
		}

		tween.elapsed += delta;

		let tween_target = target.get();
		let is_complete = tween.elapsed >= tween.duration;
		let progress = (tween.elapsed.as_secs_f32() / tween.duration.as_secs_f32()).clamp(0.0, 1.0);

		let despawn = if let Ok(mut cmp) = components.get_mut(tween_target) {
			T::interpolate(&mut cmp, progress, &tween.easing);
			false
		} else {
			true
		};

		if is_complete {
			if let Some(user_data) = tween.user_data {
				if mode.is_once() {
					let event = TweenComplete {
						entity: tween_target,
						user_data,
					};
					complete_events.write(event);
					commands.trigger_targets(event, tween_target);
				} else {
					let event = TweenLooped {
						entity: tween_target,
						user_data,
					};
					looped_events.write(event);
					commands.trigger_targets(event, tween_target);
				}
			}
		}

		if despawn {
			commands.entity(entity).despawn();
		} else if is_complete {
			match mode {
				TweenMode::PingPong => {
					tween.easing = tween.easing.flip();
					tween.elapsed = Duration::ZERO;
				}
				TweenMode::Loop => {
					tween.elapsed = Duration::ZERO;
				}
				TweenMode::Once => {
					commands.entity(entity).despawn();
				}
			}
		}
	}
}

pub fn remove_tween<T: Tweenable>(
	In(target): In<Entity>,
	mut commands: Commands,
	query: Query<(Entity, &TweenTarget), With<Tween<T>>>,
) {
	for (entity, tween_target) in &query {
		if tween_target.get() == target {
			commands.entity(entity).despawn();
		}
	}
}

pub fn update_or_create_tween<T: Tweenable>(
	In(options): In<UpdateTween<T>>,
	mut commands: Commands,
	mut tweeners: Query<(&mut Tween<T>, &TweenTarget)>,
	data_source: Query<&T::Comp>,
) {
	let target = options.entity;
	if let Ok(value) = data_source.get(target) {
		let value = T::current_value(value);
		if let Some((mut tween, _)) = tweeners
			.iter_mut()
			.find(|(_, tween_target)| tween_target.get() == target)
		{
			tween.elapsed = Duration::ZERO;
			tween.duration = options.duration;
			tween.easing = TweenEasing::new(value, options.end, options.easing);
			match options.user_data {
				UpdateUserData::Set(value) => tween.user_data = Some(value),
				UpdateUserData::Remove => tween.user_data = None,
				UpdateUserData::Unchanged => {}
			}
		} else {
			commands.entity(target).with_related::<TweenTarget>(
				T::tween(options.duration, value, options.end, options.easing)
					.with_user_data(options.user_data.into()),
			);
		}
	}
}