From 5c74f90bde5e1d3036a18986d754dbd8bc059c7d Mon Sep 17 00:00:00 2001
From: Louis <contact@louiscap.co>
Date: Sun, 4 May 2025 07:48:30 +0100
Subject: [PATCH] Add documentation, fix component naming

---
 Cargo.toml        |   8 +-
 README.md         | 240 ++++++++++++++++++++++++++++++++++++++++++++++
 src/commands.rs   |  10 +-
 src/components.rs |  81 ++++++++++++----
 src/easing.rs     |  86 +++++++++--------
 src/events.rs     |  40 ++++++++
 src/lib.rs        |   4 +-
 src/systems.rs    |   9 +-
 8 files changed, 404 insertions(+), 74 deletions(-)
 create mode 100644 README.md

diff --git a/Cargo.toml b/Cargo.toml
index ffa7052..aa0dfe1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,8 +3,14 @@ name = "weirdboi_tween"
 version = "0.1.0"
 edition = "2024"
 
+authors = [
+    "Louis Capitanchik <louis@weirdboi.com>",
+]
+description = "Relationship based component value tweening for Bevy projects"
+license = "Apache-2.0"
+
 [features]
-default = []
+default = ["bevy_defaults"]
 reflect = ["dep:bevy_reflect"]
 bevy_defaults = [
     "dep:bevy_transform",
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..647c9a8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,240 @@
+# WeirdBoi Tween
+
+[![Crates.io](https://img.shields.io/crates/v/weirdboi_tween.svg)](https://crates.io/crates/weirdboi_tween)
+[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
+
+A component value tweening library for Bevy. Tweens become first class entities, working via the
+new relationships system.
+
+## Features
+
+- Relationship-based tweening system
+- Tween any value of any component
+- Apply multiple tweens of the same type simultaneously
+- Support for tween iteration (Once, Loop, PingPong)
+- User data events for looping & completion
+
+## Installation
+
+Add the following to your `Cargo.toml`:
+
+```toml
+[dependencies]
+weirdboi_tween = "0.1.0"
+```
+
+## Basic Usage
+
+1. Setup
+2. Tweening Components
+3. Creating Tweenables
+4. Tween Modes
+5. Tween User Events
+6. Built in Events
+
+### Setup
+
+Add the tweening plugin to your Bevy app:
+
+```rust
+use bevy::prelude::*;
+use weirdboi_tween::TweenPlugin;
+
+fn main() {
+    App::new()
+        .add_plugins(DefaultPlugins)
+        .add_plugins(TweenPlugin)
+        // Other plugins and systems
+        .run();
+}
+```
+
+### Tweening Component Properties
+
+Since tweens are relationship based entities, they can take advantage of all of the 
+relationship machinery to work with them. This means using the `Tweens` collection
+to spawn related entities for a target entity, or by creating a Tween independently
+and inserting a `TweenTarget` component.
+
+```rust
+
+fn spawn_animated_sprite(mut commands: Commands, asset_server: Res<AssetServer>) {
+    commands.spawn((
+        Sprite::from_image(asset_server.load("sprite.png")),
+        Tweens::spawn(&[(
+            TweenSpriteColour::tween(
+                Duration::from_millis(250),
+                Color::WHITE.into(),
+                Color::BLACK.into(),
+                EaseTween::Linear,
+            ),
+            TweenTransformTranslation::tween(
+                Duration::from_millis(250),
+                Vec3::splat(0.0),
+                Vec3::new(20.0, 50.0, 0.0),
+                EaseTween::Linear,
+            ),
+        )])
+    ));
+}
+
+```
+
+### Creating custom Tweenables
+
+The tween system requires the implementation of `Tweenable` meta types, so named because they 
+exist at the type level. A `Tweenable` is associated with one component type, and one data type.
+The data type represents some facet of the component that can have an easing applied to it - 
+this also means the data type does not need to match the type of the component property being 
+tweened.
+
+Once you have a type implementing `Tweenable`, you need to register it with the application
+in order to set up the required systems.
+
+```rust
+struct TileOffset(Vec2);
+
+struct TweenTileOffset;
+impl Tweenable for TweenTileOffset {
+    type Comp = TileOffset;
+    type Data = Vec2;
+    fn current_value(cmp: &Self::Comp) -> Self::Data {
+        cmp.0
+    }
+    fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
+        cmp.0 = value;
+    }
+}
+
+// .. Then register in a plugin
+fn TileTweenPlugin(app: &mut App) {
+    app.register_tweenable::<TweenTileOffset>();
+}
+
+
+// .. Then you can spawn the tween
+fn tween_player_offset(mut commands: Commands, marked_entity: Single<Entity, With<Player>>) {
+    commands.spawn((
+        TweenTarget(*marked_entity),
+        TweenTileOffset::tween(
+            Duration::from_millis(200),
+            Vec2::splat(0.0),
+            Vec2::new(50., 50.),
+            TweenEasing::Linear,
+        ),
+    ))
+}
+```
+
+### Tween Modes
+
+By default a tween will run once, complete, and then despawn. This behaviour can be controlled by including
+a `TweenMode` component alongside a tween. `TweenMode` has three variants:
+
+- `Once`: Default behaviour, tween runs from start to end and stops
+- `Loop`: The tween runs from start to end, and then resets to continue running from start to end indefinitely. E.g. `1, 2, 3, loop, 1, 2, 3, loop, 1, 2, 3`
+- `PingPong`: The tween runs from start to end, and the flips values to run from end to start indefinitely. E.g. `1, 2, 3, loop, 3, 2, 1, loop, 1, 2, 3`
+
+### Tween Events
+
+Any tween can have an arbitrary `u32` value associated with it, which will cause an event to be fired
+in one of two situations, depending on the mode. 
+
+When attaching user data to a one shot tween (`TweenMode::Once`, the default), a `TweenComplete` event is
+fired once the tween completes. This can either be responded to with another system taking an 
+`EventReader<TweenComplete>` parameter, or by directly attaching an observer to the _target_ entity that 
+takes a `Trigger<TweenComplete>`
+
+When attaching user data to a looping tween (`TweenMode::PingPong` or `TweenMode::Loop`), a `TweenLooped` event
+is fired each time the tween iterates. Much like the `TweenComplete` event, either an `EventReader<TweenLooped>`
+system or `Trigger<TweenLooped>` observer can be used to respond to this action.
+
+```rust
+fn handle_tween_complete(mut events: EventReader<TweenComplete>) {
+    for event in events.read() {
+        println!("Tween completed for entity: {:?}", event.entity);
+        
+        if event.user_data == MY_CUSTOM_EVENT_ID {
+            // Handle specific tween completion
+        }
+    }
+}
+```
+
+### Automatic Despawn
+
+There are two utility events built in for the common case of animating throwaway entities. While the
+tween entity will always despawn when complete, using the `DESPAWN_ON_TWEEN_COMPLETE_EVENT` user data
+will also cause the tween's target to despawn when the tween completes:
+
+```rust
+fn spawn_a_tweenable(mut commands: Commands) {
+    commands.spawn((
+        Transform::default(),
+        Sprite::default(),
+        TweenSpriteColour::tween(
+            Duration::from_millis(200),
+            Color::WHITE.into(),
+            Color::WHITE.with_alpha(0.0).into(),
+            TweenEasing::Linear
+        )
+            .with_user_data(DESPAWN_ON_TWEEN_COMPLETE_EVENT)
+            .spawn()
+    ));
+}
+```
+
+While less common, it can also be useful to despawn an entire hierarchy (above and below) when the tween completes 
+(e.g. fading out a UI element that is not a UI root due to styling constraints).
+
+This can be achieved with the `DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT` user data:
+
+```rust
+fn spawn_a_tweenable_leaf(mut commands: Commands) {
+    commands.spawn((
+        Node {
+            // Some styling
+            ..default()
+        },
+        children![(
+            Text::new("My label"),
+            TextColor::from(Color::WHITE),
+            TweenTextColour::tween(
+                Duration::from_millis(200),
+                Color::WHITE.into(),
+                Color::WHITE.with_alpha(0.0).into(),
+                TweenEasing::Linear
+            )
+                .with_user_data(DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT)
+                .spawn()
+        )]
+    ));
+}
+```
+
+## Available Easing Functions
+
+WeirdBoi Tween provides a variety of easing functions, forked from `bevy_math`, made available 
+under the `TweenEasing` enum:
+
+- Linear
+- QuadraticIn, QuadraticOut, QuadraticInOut
+- CubicIn, CubicOut, CubicInOut
+- QuarticIn, QuarticOut, QuarticInOut
+- QuinticIn, QuinticOut, QuinticInOut
+- SineIn, SineOut, SineInOut
+- CircularIn, CircularOut, CircularInOut
+- ExponentialIn, ExponentialOut, ExponentialInOut
+- ElasticIn, ElasticOut, ElasticInOut
+- BackIn, BackOut, BackInOut
+- BounceIn, BounceOut, BounceInOut
+
+## Built-in Tweenable Types
+
+By enabling the `bevy_defaults` feature, you get access to the 
+
+- `TweenTransformTranslation` - Tweens a Transform's position
+- `TweenTransformScale` - Tweens a Transform's scale
+- `TweenSpriteColor` - Tweens a Sprite's color
+- `TweenImageNodeColour` - Tweens a Sprite's color
+- `TweenTextColour` - Tweens a Sprite's color
diff --git a/src/commands.rs b/src/commands.rs
index 5a9c486..e14a019 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -1,5 +1,5 @@
 use crate::components::Tweenable;
-use crate::easing::EaseTween;
+use crate::easing::TweenEasing;
 use crate::systems::{remove_tween, tick_tweens, update_or_create_tween};
 use crate::{UpdateTween, UpdateUserData};
 use bevy_app::{App, Update};
@@ -39,7 +39,7 @@ pub trait UpdateTweenExt {
 		entity: Entity,
 		duration: impl Into<Duration>,
 		end: T::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 		event: impl Into<UpdateUserData>,
 	) -> &mut Self;
 }
@@ -48,7 +48,7 @@ pub trait UpdateOwnTweenExt {
 		&mut self,
 		duration: impl Into<Duration>,
 		end: T::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 		event: impl Into<UpdateUserData>,
 	) -> &mut Self;
 }
@@ -59,7 +59,7 @@ impl UpdateTweenExt for Commands<'_, '_> {
 		entity: Entity,
 		duration: impl Into<Duration>,
 		end: T::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 		event: impl Into<UpdateUserData>,
 	) -> &mut Self {
 		let update = UpdateTween::<T> {
@@ -84,7 +84,7 @@ impl UpdateOwnTweenExt for EntityCommands<'_> {
 		&mut self,
 		duration: impl Into<Duration>,
 		end: T::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 		event: impl Into<UpdateUserData>,
 	) -> &mut Self {
 		let entity = self.id();
diff --git a/src/components.rs b/src/components.rs
index 0ddaad5..e469c4b 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -1,6 +1,8 @@
-use crate::easing::EaseTween;
+use crate::TweenEasing;
 use bevy_ecs::component::{Component, Mutable};
 use bevy_ecs::entity::Entity;
+use bevy_ecs::prelude::Bundle;
+use bevy_ecs::spawn::SpawnRelated;
 use bevy_math::Curve;
 use bevy_math::curve::{Ease, Interval};
 use std::fmt::{Debug, Formatter};
@@ -14,7 +16,7 @@ pub trait Tweenable: Send + Sync + 'static {
 		duration: impl Into<Duration>,
 		from: Self::Data,
 		to: Self::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 	) -> Tween<Self>
 	where
 		Self: Sized,
@@ -27,7 +29,7 @@ pub trait Tweenable: Send + Sync + 'static {
 		delay: impl Into<Duration>,
 		from: Self::Data,
 		to: Self::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 	) -> Tween<Self>
 	where
 		Self: Sized,
@@ -35,12 +37,12 @@ pub trait Tweenable: Send + Sync + 'static {
 		Tween::delayed(duration, delay, from, to, easing)
 	}
 
-	fn sample(progress: f32, easing: &TweenEasing<Self::Data>) -> Self::Data {
+	fn sample(progress: f32, easing: &TweenCurve<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>) {
+	fn interpolate(cmp: &mut Self::Comp, progress: f32, easing: &TweenCurve<Self::Data>) {
 		let value = Self::sample(progress, easing);
 		Self::update_component(cmp, value);
 	}
@@ -69,17 +71,17 @@ pub struct Tween<T: Tweenable> {
 	pub delay: Duration,
 	pub duration: Duration,
 	pub elapsed: Duration,
-	pub easing: TweenEasing<T::Data>,
+	pub easing: TweenCurve<T::Data>,
 	pub user_data: Option<u32>,
 }
 
 #[derive(Component)]
-#[relationship(relationship_target = ActiveTweens)]
+#[relationship(relationship_target = Tweens)]
 pub struct TweenTarget(pub Entity);
 
 #[derive(Component)]
 #[relationship_target(relationship = TweenTarget)]
-pub struct ActiveTweens(Vec<Entity>);
+pub struct Tweens(Vec<Entity>);
 
 impl<T: Tweenable> Debug for Tween<T> {
 	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -89,7 +91,7 @@ impl<T: Tweenable> Debug for Tween<T> {
 			.field("elapsed", &self.elapsed)
 			.field(
 				"easing",
-				&format!("TweenEasing<{}>", std::any::type_name::<T::Data>()),
+				&format!("TweenCurve<{}>", std::any::type_name::<T::Data>()),
 			)
 			.field("user_data", &self.user_data)
 			.finish()
@@ -117,7 +119,7 @@ impl<T: Tweenable> Tween<T> {
 		duration: impl Into<Duration>,
 		from: T::Data,
 		to: T::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 	) -> Self {
 		Self::delayed(duration, Duration::ZERO, from, to, easing)
 	}
@@ -127,9 +129,9 @@ impl<T: Tweenable> Tween<T> {
 		delay: impl Into<Duration>,
 		from: T::Data,
 		to: T::Data,
-		easing: EaseTween,
+		easing: TweenEasing,
 	) -> Self {
-		let easing = TweenEasing::new(from, to, easing);
+		let easing = TweenCurve::new(from, to, easing);
 		Self {
 			delay: delay.into(),
 			duration: duration.into(),
@@ -140,12 +142,49 @@ impl<T: Tweenable> Tween<T> {
 	}
 
 	pub fn linear(duration: impl Into<Duration>, from: T::Data, to: T::Data) -> Self {
-		Tween::new(duration, from, to, EaseTween::Linear)
+		Tween::new(duration, from, to, TweenEasing::Linear)
 	}
 
 	pub fn with_user_data(self, user_data: Option<u32>) -> Self {
 		Self { user_data, ..self }
 	}
+
+	/// Spawn this tween as a related entity instead of attaching it directly to
+	/// an entity being spawned.
+	///
+	/// ### Example
+	///
+	/// ```no_run
+	/// # use std::time::Duration;
+	/// # use bevy_ecs::component::Component;
+	/// # use bevy_ecs::system::Commands;
+	/// # use bevy_math::Vec3;
+	/// # use bevy_transform::components::Transform;
+	/// use weirdboi_tween::{TweenEasing, Tween, TweenTransformTranslation, Tweenable};
+	///
+	/// #[derive(Component)]
+	/// struct Widget(pub usize);
+	///
+	/// fn spawn_example(mut commands: Commands) {
+	///     commands.spawn((
+	///         Transform::default(),
+	///         Widget(200),
+	///         TweenTransformTranslation::tween(
+	///             Duration::from_millis(200),
+	///             Vec3::new(0.0, 0.0, 0.0),
+	///             Vec3::new(50.0, 35.0, 0.0),
+	///             TweenEasing::Linear,
+	///         ).spawn()
+	///     ));
+	/// }
+	/// ```
+	pub fn spawn(self) -> impl Bundle {
+		Tweens::spawn_one((self,))
+	}
+
+	pub fn spawn_with_mode(self, mode: TweenMode) -> impl Bundle {
+		Tweens::spawn_one((self, mode))
+	}
 }
 
 #[derive(Copy, Clone, Default, Debug)]
@@ -180,19 +219,19 @@ pub struct UpdateTween<T: Tweenable> {
 	pub entity: Entity,
 	pub duration: Duration,
 	pub end: T::Data,
-	pub easing: EaseTween,
+	pub easing: TweenEasing,
 	pub user_data: UpdateUserData,
 }
 
 #[derive(Clone, Debug)]
-pub struct TweenEasing<T> {
+pub struct TweenCurve<T> {
 	start: T,
 	end: T,
-	ease_fn: EaseTween,
+	ease_fn: TweenEasing,
 }
 
-impl<T: Clone> TweenEasing<T> {
-	pub fn new(start: T, end: T, ease_fn: EaseTween) -> Self {
+impl<T: Clone> TweenCurve<T> {
+	pub fn new(start: T, end: T, ease_fn: TweenEasing) -> Self {
 		Self {
 			start,
 			end,
@@ -208,12 +247,12 @@ impl<T: Clone> TweenEasing<T> {
 		&self.end
 	}
 
-	pub fn easing(&self) -> EaseTween {
+	pub fn easing(&self) -> TweenEasing {
 		self.ease_fn
 	}
 
 	pub fn flip(&self) -> Self {
-		TweenEasing {
+		TweenCurve {
 			start: self.end.clone(),
 			end: self.start.clone(),
 			ease_fn: self.ease_fn,
@@ -221,7 +260,7 @@ impl<T: Clone> TweenEasing<T> {
 	}
 }
 
-impl<T> Curve<T> for TweenEasing<T>
+impl<T> Curve<T> for TweenCurve<T>
 where
 	T: Ease + Clone,
 {
diff --git a/src/easing.rs b/src/easing.rs
index 70c4bd2..cb6444e 100644
--- a/src/easing.rs
+++ b/src/easing.rs
@@ -1,6 +1,12 @@
+//!
+//! This module is directly vendored from the bevy_math crate
+//! The contents were not public, and made transforming curves
+//! near impossible
+//!
+
 #[non_exhaustive]
 #[derive(Debug, Copy, Clone, PartialEq)]
-pub enum EaseTween {
+pub enum TweenEasing {
 	Linear,
 	QuadraticIn,
 	QuadraticOut,
@@ -41,47 +47,47 @@ pub enum EaseTween {
 	Elastic(f32),
 }
 
-impl EaseTween {
+impl TweenEasing {
 	pub fn eval(&self, t: f32) -> f32 {
 		match self {
-			EaseTween::Linear => easing_functions::linear(t),
-			EaseTween::QuadraticIn => easing_functions::quadratic_in(t),
-			EaseTween::QuadraticOut => easing_functions::quadratic_out(t),
-			EaseTween::QuadraticInOut => easing_functions::quadratic_in_out(t),
-			EaseTween::CubicIn => easing_functions::cubic_in(t),
-			EaseTween::CubicOut => easing_functions::cubic_out(t),
-			EaseTween::CubicInOut => easing_functions::cubic_in_out(t),
-			EaseTween::QuarticIn => easing_functions::quartic_in(t),
-			EaseTween::QuarticOut => easing_functions::quartic_out(t),
-			EaseTween::QuarticInOut => easing_functions::quartic_in_out(t),
-			EaseTween::QuinticIn => easing_functions::quintic_in(t),
-			EaseTween::QuinticOut => easing_functions::quintic_out(t),
-			EaseTween::QuinticInOut => easing_functions::quintic_in_out(t),
-			EaseTween::SmoothStepIn => easing_functions::smoothstep_in(t),
-			EaseTween::SmoothStepOut => easing_functions::smoothstep_out(t),
-			EaseTween::SmoothStep => easing_functions::smoothstep(t),
-			EaseTween::SmootherStepIn => easing_functions::smootherstep_in(t),
-			EaseTween::SmootherStepOut => easing_functions::smootherstep_out(t),
-			EaseTween::SmootherStep => easing_functions::smootherstep(t),
-			EaseTween::SineIn => easing_functions::sine_in(t),
-			EaseTween::SineOut => easing_functions::sine_out(t),
-			EaseTween::SineInOut => easing_functions::sine_in_out(t),
-			EaseTween::CircularIn => easing_functions::circular_in(t),
-			EaseTween::CircularOut => easing_functions::circular_out(t),
-			EaseTween::CircularInOut => easing_functions::circular_in_out(t),
-			EaseTween::ExponentialIn => easing_functions::exponential_in(t),
-			EaseTween::ExponentialOut => easing_functions::exponential_out(t),
-			EaseTween::ExponentialInOut => easing_functions::exponential_in_out(t),
-			EaseTween::ElasticIn => easing_functions::elastic_in(t),
-			EaseTween::ElasticOut => easing_functions::elastic_out(t),
-			EaseTween::ElasticInOut => easing_functions::elastic_in_out(t),
-			EaseTween::BackIn => easing_functions::back_in(t),
-			EaseTween::BackOut => easing_functions::back_out(t),
-			EaseTween::BackInOut => easing_functions::back_in_out(t),
-			EaseTween::BounceIn => easing_functions::bounce_in(t),
-			EaseTween::BounceOut => easing_functions::bounce_out(t),
-			EaseTween::BounceInOut => easing_functions::bounce_in_out(t),
-			EaseTween::Elastic(omega) => easing_functions::elastic(*omega, t),
+			TweenEasing::Linear => easing_functions::linear(t),
+			TweenEasing::QuadraticIn => easing_functions::quadratic_in(t),
+			TweenEasing::QuadraticOut => easing_functions::quadratic_out(t),
+			TweenEasing::QuadraticInOut => easing_functions::quadratic_in_out(t),
+			TweenEasing::CubicIn => easing_functions::cubic_in(t),
+			TweenEasing::CubicOut => easing_functions::cubic_out(t),
+			TweenEasing::CubicInOut => easing_functions::cubic_in_out(t),
+			TweenEasing::QuarticIn => easing_functions::quartic_in(t),
+			TweenEasing::QuarticOut => easing_functions::quartic_out(t),
+			TweenEasing::QuarticInOut => easing_functions::quartic_in_out(t),
+			TweenEasing::QuinticIn => easing_functions::quintic_in(t),
+			TweenEasing::QuinticOut => easing_functions::quintic_out(t),
+			TweenEasing::QuinticInOut => easing_functions::quintic_in_out(t),
+			TweenEasing::SmoothStepIn => easing_functions::smoothstep_in(t),
+			TweenEasing::SmoothStepOut => easing_functions::smoothstep_out(t),
+			TweenEasing::SmoothStep => easing_functions::smoothstep(t),
+			TweenEasing::SmootherStepIn => easing_functions::smootherstep_in(t),
+			TweenEasing::SmootherStepOut => easing_functions::smootherstep_out(t),
+			TweenEasing::SmootherStep => easing_functions::smootherstep(t),
+			TweenEasing::SineIn => easing_functions::sine_in(t),
+			TweenEasing::SineOut => easing_functions::sine_out(t),
+			TweenEasing::SineInOut => easing_functions::sine_in_out(t),
+			TweenEasing::CircularIn => easing_functions::circular_in(t),
+			TweenEasing::CircularOut => easing_functions::circular_out(t),
+			TweenEasing::CircularInOut => easing_functions::circular_in_out(t),
+			TweenEasing::ExponentialIn => easing_functions::exponential_in(t),
+			TweenEasing::ExponentialOut => easing_functions::exponential_out(t),
+			TweenEasing::ExponentialInOut => easing_functions::exponential_in_out(t),
+			TweenEasing::ElasticIn => easing_functions::elastic_in(t),
+			TweenEasing::ElasticOut => easing_functions::elastic_out(t),
+			TweenEasing::ElasticInOut => easing_functions::elastic_in_out(t),
+			TweenEasing::BackIn => easing_functions::back_in(t),
+			TweenEasing::BackOut => easing_functions::back_out(t),
+			TweenEasing::BackInOut => easing_functions::back_in_out(t),
+			TweenEasing::BounceIn => easing_functions::bounce_in(t),
+			TweenEasing::BounceOut => easing_functions::bounce_out(t),
+			TweenEasing::BounceInOut => easing_functions::bounce_in_out(t),
+			TweenEasing::Elastic(omega) => easing_functions::elastic(*omega, t),
 		}
 	}
 }
diff --git a/src/events.rs b/src/events.rs
index d0370b1..d79791a 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -4,14 +4,54 @@ use bevy_ecs::event::Event;
 pub const DESPAWN_ON_TWEEN_COMPLETE_EVENT: u32 = u32::MAX;
 pub const DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT: u32 = u32::MAX - 1;
 
+/// An event fired when a tween using `TweenMode::Once` has finished
+///
+/// ```rust
+/// use bevy_ecs::event::EventReader;
+/// use weirdboi_tween::TweenComplete;
+///
+/// const DO_A_BACKFLIP_ON_COMPLETE: u32 = 123;
+///
+/// fn on_tween_complete(mut events: EventReader<TweenComplete>) {
+///     for event in events.read() {
+///         if *event == DO_A_BACKFLIP_ON_COMPLETE {
+///             log::debug!("Doing a backflip on entity {:?}", event.entity);
+///         }
+///     }
+/// }
+/// ```
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
 pub struct TweenComplete {
 	pub entity: Entity,
 	pub user_data: u32,
 }
 
+impl PartialEq<Entity> for TweenComplete {
+	fn eq(&self, other: &Entity) -> bool {
+		self.entity == *other
+	}
+}
+
+impl PartialEq<u32> for TweenComplete {
+	fn eq(&self, other: &u32) -> bool {
+		self.user_data == *other
+	}
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
 pub struct TweenLooped {
 	pub entity: Entity,
 	pub user_data: u32,
 }
+
+impl PartialEq<Entity> for TweenLooped {
+	fn eq(&self, other: &Entity) -> bool {
+		self.entity == *other
+	}
+}
+
+impl PartialEq<u32> for TweenLooped {
+	fn eq(&self, other: &u32) -> bool {
+		self.user_data == *other
+	}
+}
diff --git a/src/lib.rs b/src/lib.rs
index 0251f1f..5e7714b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,9 +15,9 @@ pub use bevy_tweenables::{
 };
 pub use commands::{ClearTweensExt, RegisterTweenableExt, UpdateOwnTweenExt, UpdateTweenExt};
 pub use components::{
-	ActiveTweens, Tween, TweenMode, TweenTarget, Tweenable, UpdateTween, UpdateUserData,
+	Tween, TweenCurve, TweenMode, TweenTarget, Tweenable, Tweens, UpdateTween, UpdateUserData,
 };
-pub use easing::{EaseTween, easing_functions};
+pub use easing::{TweenEasing, easing_functions};
 pub use events::{
 	DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT, DESPAWN_ON_TWEEN_COMPLETE_EVENT, TweenComplete,
 	TweenLooped,
diff --git a/src/systems.rs b/src/systems.rs
index a41e002..cbb0968 100644
--- a/src/systems.rs
+++ b/src/systems.rs
@@ -1,8 +1,7 @@
-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,
+	DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT, DESPAWN_ON_TWEEN_COMPLETE_EVENT, Tween,
+	TweenComplete, TweenCurve, TweenEasing, TweenLooped, TweenMode, TweenTarget, Tweenable,
+	UpdateTween, UpdateUserData,
 };
 use bevy_ecs::entity::Entity;
 use bevy_ecs::event::{EventReader, EventWriter};
@@ -138,7 +137,7 @@ pub fn update_or_create_tween<T: Tweenable>(
 		{
 			tween.elapsed = Duration::ZERO;
 			tween.duration = options.duration;
-			tween.easing = TweenEasing::new(value, options.end, options.easing);
+			tween.easing = TweenCurve::new(value, options.end, options.easing);
 			match options.user_data {
 				UpdateUserData::Set(value) => tween.user_data = Some(value),
 				UpdateUserData::Remove => tween.user_data = None,
-- 
GitLab