Skip to content
Snippets Groups Projects
components.rs 4.86 KiB
Newer Older
Louis's avatar
Louis committed
use crate::easing::EaseTween;
use bevy_ecs::component::{Component, Mutable};
use bevy_ecs::entity::Entity;
use bevy_math::Curve;
use bevy_math::curve::{Ease, Interval};
use std::fmt::{Debug, Formatter};
use std::time::Duration;

pub trait Tweenable: Send + Sync + 'static {
	type Comp: Component<Mutability = Mutable>;
	type Data: Ease + Clone + Send + Sync + 'static;

	fn tween(
		duration: impl Into<Duration>,
		from: Self::Data,
		to: Self::Data,
		easing: EaseTween,
	) -> Tween<Self>
	where
		Self: Sized,
	{
		Self::delayed(duration, Duration::ZERO, from, to, easing)
	}

	fn delayed(
		duration: impl Into<Duration>,
		delay: impl Into<Duration>,
		from: Self::Data,
		to: Self::Data,
		easing: EaseTween,
	) -> Tween<Self>
	where
		Self: Sized,
	{
		Tween::delayed(duration, delay, from, to, easing)
	}

	fn sample(progress: f32, easing: &TweenEasing<Self::Data>) -> Self::Data {
		easing.sample_clamped(progress)
	}
	fn current_value(cmp: &Self::Comp) -> Self::Data;
	fn update_component(cmp: &mut Self::Comp, value: Self::Data);
	fn interpolate(cmp: &mut Self::Comp, progress: f32, easing: &TweenEasing<Self::Data>) {
		let value = Self::sample(progress, easing);
		Self::update_component(cmp, value);
	}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Component, Default)]
pub enum TweenMode {
	#[default]
	Once,
	PingPong,
	Loop,
}

impl TweenMode {
	pub fn is_loop(&self) -> bool {
		matches!(self, Self::PingPong | Self::Loop)
	}
	pub fn is_once(&self) -> bool {
		matches!(self, Self::Once)
	}
}

#[derive(Component)]
#[require(TweenMode)]
pub struct Tween<T: Tweenable> {
	pub delay: Duration,
	pub duration: Duration,
	pub elapsed: Duration,
	pub easing: TweenEasing<T::Data>,
	pub user_data: Option<u32>,
}

#[derive(Component)]
#[relationship(relationship_target = ActiveTweens)]
pub struct TweenTarget(pub Entity);

#[derive(Component)]
#[relationship_target(relationship = TweenTarget)]
pub struct ActiveTweens(Vec<Entity>);

impl<T: Tweenable> Debug for Tween<T> {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("Tween<T>")
			.field("delay", &self.delay)
			.field("duration", &self.duration)
			.field("elapsed", &self.elapsed)
			.field(
				"easing",
				&format!("TweenEasing<{}>", std::any::type_name::<T::Data>()),
			)
			.field("user_data", &self.user_data)
			.finish()
	}
}

impl<T> Clone for Tween<T>
where
	T: Tweenable,
	T::Data: Clone,
{
	fn clone(&self) -> Self {
		Self {
			delay: self.delay,
			duration: self.duration,
			elapsed: self.elapsed,
			easing: self.easing.clone(),
			user_data: self.user_data,
		}
	}
}

impl<T: Tweenable> Tween<T> {
	pub fn new(
		duration: impl Into<Duration>,
		from: T::Data,
		to: T::Data,
		easing: EaseTween,
	) -> Self {
		Self::delayed(duration, Duration::ZERO, from, to, easing)
	}

	pub fn delayed(
		duration: impl Into<Duration>,
		delay: impl Into<Duration>,
		from: T::Data,
		to: T::Data,
		easing: EaseTween,
	) -> Self {
		let easing = TweenEasing::new(from, to, easing);
		Self {
			delay: delay.into(),
			duration: duration.into(),
			elapsed: Duration::ZERO,
			easing,
			user_data: None,
		}
	}

	pub fn linear(duration: impl Into<Duration>, from: T::Data, to: T::Data) -> Self {
		Tween::new(duration, from, to, EaseTween::Linear)
	}

	pub fn with_user_data(self, user_data: Option<u32>) -> Self {
		Self { user_data, ..self }
	}
}

#[derive(Copy, Clone, Default, Debug)]
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
pub enum UpdateUserData {
	#[default]
	Unchanged,
	Set(u32),
	Remove,
}

impl From<Option<u32>> for UpdateUserData {
	fn from(value: Option<u32>) -> Self {
		match value {
			Some(value) => UpdateUserData::Set(value),
			None => UpdateUserData::Unchanged,
		}
	}
}

impl From<UpdateUserData> for Option<u32> {
	fn from(value: UpdateUserData) -> Self {
		match value {
			UpdateUserData::Unchanged => None,
			UpdateUserData::Set(value) => Some(value),
			UpdateUserData::Remove => None,
		}
	}
}

pub struct UpdateTween<T: Tweenable> {
	pub entity: Entity,
	pub duration: Duration,
	pub end: T::Data,
	pub easing: EaseTween,
	pub user_data: UpdateUserData,
}

#[derive(Clone, Debug)]
pub struct TweenEasing<T> {
	start: T,
	end: T,
	ease_fn: EaseTween,
}

impl<T: Clone> TweenEasing<T> {
	pub fn new(start: T, end: T, ease_fn: EaseTween) -> Self {
		Self {
			start,
			end,
			ease_fn,
		}
	}

	pub fn start(&self) -> &T {
		&self.start
	}

	pub fn end(&self) -> &T {
		&self.end
	}

	pub fn easing(&self) -> EaseTween {
		self.ease_fn
	}

	pub fn flip(&self) -> Self {
		TweenEasing {
			start: self.end.clone(),
			end: self.start.clone(),
			ease_fn: self.ease_fn,
		}
	}
}

impl<T> Curve<T> for TweenEasing<T>
where
	T: Ease + Clone,
{
	#[inline]
	fn domain(&self) -> Interval {
		Interval::UNIT
	}

	#[inline]
	fn sample_unchecked(&self, t: f32) -> T {
		let remapped_t = self.ease_fn.eval(t);
		T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
			.sample_unchecked(remapped_t)
	}
}