From 6d2df17b77fc50023e3bd82715fb74b29ac5a820 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Fri, 23 Oct 2026 18:42:18 +0100 Subject: [PATCH 01/34] feat: adds event traversal for tween events This lets us listen to said events on the entity that we inserted the animation on i.e. we can use: ``` commands .animation(...) .observe(...) ``` --- src/tween_event.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 9ce7e22..ce85f5c 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -25,12 +25,16 @@ //! See [`DefaultTweenEventPlugins`] for default events which is also added in //! [`DefaultTweenPlugins`](crate::DefaultTweenPlugins) -use bevy_time_runner::TimeContext; +use bevy_time_runner::{TimeContext, TimeRunner}; use std::marker::PhantomData; use bevy::{ app::PluginGroupBuilder, - ecs::schedule::{InternedScheduleLabel, ScheduleLabel}, + ecs::{ + query::QueryData, + schedule::{InternedScheduleLabel, ScheduleLabel}, + traversal::Traversal, + }, prelude::*, }; @@ -155,6 +159,7 @@ impl TweenEventData<()> { /// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity /// by [`tween_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] +#[entity_event(propagate = TweenTraversal, auto_propagate)] pub struct TweenEvent { /// Custom user data pub data: Data, @@ -166,6 +171,33 @@ pub struct TweenEvent { pub entity: Entity, } +/// Traverses up `ChildOf` components, stopping at the first `TimeRunner` +/// `TimeRunner` components are inserted on the "root" component of an animation +/// which allows us to define an entity observer for `TweenEffects` on that root. +#[derive(QueryData)] +pub struct TweenTraversal { + child_of: Option<&'static ChildOf>, + runner: Option<&'static TimeRunner>, +} + +impl Traversal> for TweenTraversal { + fn traverse(item: Self::Item<'_, '_>, _: &TweenEvent) -> Option { + let TweenTraversalItem { child_of, runner } = item; + + // Current entity is an animation "root", halt. + if runner.is_some() { + return None; + } + + // Send event to parent, if it has one. + if let Some(child_of) = child_of { + return Some(child_of.parent()); + } + + None + } +} + /// Fires [`TweenEvent`] with optional user data whenever [`TimeSpanProgress`] /// and [`TweenEventData`] exist in the same entity and data is `Some`, /// cloning the data. From 1e15a51f494ee2a214877e0cbda3e2ed3cdc853e Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Fri, 23 Oct 2026 20:09:29 +0100 Subject: [PATCH 02/34] feat: adds entity observers to examples --- examples/demo/entity_event.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/demo/entity_event.rs b/examples/demo/entity_event.rs index 0d350d2..5d62d9b 100644 --- a/examples/demo/entity_event.rs +++ b/examples/demo/entity_event.rs @@ -10,7 +10,7 @@ fn main() { .add_plugins((MinimalPlugins, DefaultTweenPlugins::default())) .add_systems(Startup, setup) .add_observer(|trigger: On>| { - println!("TweenEvent: {}", trigger.data) + println!("TweenEvent on {}: {}", trigger.entity, trigger.data) }) .add_observer(|trigger: On| { if trigger.is_completed() { @@ -28,10 +28,15 @@ fn setup(mut commands: Commands) { .repeat(Repeat::times(5)) .insert(forward(Duration::from_secs_f32(0.5))); - commands.animation().insert(sequence(( - forward(Duration::from_secs_f32(3.)), - event("tween"), - forward(Duration::from_secs_f32(0.5)), - event("event"), - ))); + commands + .animation() + .insert(sequence(( + forward(Duration::from_secs_f32(3.)), + event("tween"), + forward(Duration::from_secs_f32(0.5)), + event("event"), + ))) + .observe(|trigger: On>| { + println!("TweenEvent on root: {}", trigger.data) + }); } From 05b8bf850f4bb05b4edefb9228bef5aa0cb574a2 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 18:51:52 +0100 Subject: [PATCH 03/34] tweak: renames event emitter marker + removes hard requirement on emitter marker allowing us to use different markers with `TweenEventData` --- src/combinator/animation_combinators.rs | 6 +++++- src/tween_event.rs | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 9eb9b79..584cb7c 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -1,5 +1,5 @@ use super::AnimationCommands; -use crate::prelude::TweenEventData; +use crate::{prelude::TweenEventData, tween_event::SelfEventEmittingTween}; use bevy::prelude::*; use bevy_time_runner::TimeSpan; use std::time::Duration; @@ -98,6 +98,7 @@ where a.spawn(( TimeSpan::try_from(*pos..=*pos).unwrap(), TweenEventData::with_data(event_data), + SelfEventEmittingTween, )); } } @@ -124,6 +125,7 @@ where a.spawn(( TimeSpan::try_from(at..=at).unwrap(), TweenEventData::with_data(event_data), + SelfEventEmittingTween, )); } } @@ -152,6 +154,7 @@ where a.spawn(( TimeSpan::try_from(start..end).unwrap(), TweenEventData::with_data(event_data), + SelfEventEmittingTween, )); *pos = end; } @@ -181,6 +184,7 @@ where a.spawn(( span.try_into().unwrap(), TweenEventData::with_data(event_data), + SelfEventEmittingTween, )); } } diff --git a/src/tween_event.rs b/src/tween_event.rs index ce85f5c..d43fab3 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -132,15 +132,14 @@ impl Default for DefaultTweenEventPlugins<()> { /// Fires [`TweenEvent`] whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity. #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] -#[require(EventEmittingTween)] pub struct TweenEventData(pub Data) where Data: Send + Sync + 'static; -/// Used to mark event-emitting tweens (tweens with `TweenEventData` for some registered `Data`) +/// Used to mark self event-emitting tween (tweens that emit an entity event to the tweening component) #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] -pub struct EventEmittingTween; +pub struct SelfEventEmittingTween; impl TweenEventData { /// Create new [`TweenEventData`] with custom user data. From c852465b80afee2461b717debd5615791cf30d3c Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 18:52:30 +0100 Subject: [PATCH 04/34] revert: removes traversal ahead of splitting event propagation --- src/tween_event.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index d43fab3..c2e290a 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -158,7 +158,6 @@ impl TweenEventData<()> { /// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity /// by [`tween_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] -#[entity_event(propagate = TweenTraversal, auto_propagate)] pub struct TweenEvent { /// Custom user data pub data: Data, @@ -170,33 +169,6 @@ pub struct TweenEvent { pub entity: Entity, } -/// Traverses up `ChildOf` components, stopping at the first `TimeRunner` -/// `TimeRunner` components are inserted on the "root" component of an animation -/// which allows us to define an entity observer for `TweenEffects` on that root. -#[derive(QueryData)] -pub struct TweenTraversal { - child_of: Option<&'static ChildOf>, - runner: Option<&'static TimeRunner>, -} - -impl Traversal> for TweenTraversal { - fn traverse(item: Self::Item<'_, '_>, _: &TweenEvent) -> Option { - let TweenTraversalItem { child_of, runner } = item; - - // Current entity is an animation "root", halt. - if runner.is_some() { - return None; - } - - // Send event to parent, if it has one. - if let Some(child_of) = child_of { - return Some(child_of.parent()); - } - - None - } -} - /// Fires [`TweenEvent`] with optional user data whenever [`TimeSpanProgress`] /// and [`TweenEventData`] exist in the same entity and data is `Some`, /// cloning the data. From 2fdda83c3936a0ea8a9430bdd9e23fd3f292cf29 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 18:52:59 +0100 Subject: [PATCH 05/34] feat: adds new markers for different event types --- src/tween_event.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tween_event.rs b/src/tween_event.rs index c2e290a..49c1752 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -141,6 +141,16 @@ where #[reflect(Component)] pub struct SelfEventEmittingTween; +/// Used to mark message-emitting tweens (tweens that emit a message with `message.data: Data`) +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] +#[reflect(Component)] +pub struct MessageEmittingTween; + +/// Used to mark root event-emitting tween (tweens that emit an entity event to the animation root) +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] +#[reflect(Component)] +pub struct RootEventEmittingTween; + impl TweenEventData { /// Create new [`TweenEventData`] with custom user data. pub fn with_data(data: Data) -> Self { From e4303570641445dbc7a3b75b3f87b14c9da22051 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 18:53:22 +0100 Subject: [PATCH 06/34] chore: removes unused imports --- src/tween_event.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 49c1752..9d41fb0 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -25,16 +25,12 @@ //! See [`DefaultTweenEventPlugins`] for default events which is also added in //! [`DefaultTweenPlugins`](crate::DefaultTweenPlugins) -use bevy_time_runner::{TimeContext, TimeRunner}; +use bevy_time_runner::TimeContext; use std::marker::PhantomData; use bevy::{ app::PluginGroupBuilder, - ecs::{ - query::QueryData, - schedule::{InternedScheduleLabel, ScheduleLabel}, - traversal::Traversal, - }, + ecs::schedule::{InternedScheduleLabel, ScheduleLabel}, prelude::*, }; From ec977a977fde66e1bf1c98a956d0969c6f403e92 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 19:07:59 +0100 Subject: [PATCH 07/34] feat: adds listener systems for other event types --- src/lib.rs | 2 +- src/tween_event.rs | 91 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f4c5290..ce937b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -418,7 +418,7 @@ pub use tween::component_tween_system; pub use tween::resource_dyn_tween_system; pub use tween::resource_tween_system; -pub use tween_event::tween_event_system; +pub use tween_event::tween_self_event_system; /// Default plugins for using crate. pub struct DefaultTweenPlugins diff --git a/src/tween_event.rs b/src/tween_event.rs index 9d41fb0..97f0b58 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -30,7 +30,10 @@ use std::marker::PhantomData; use bevy::{ app::PluginGroupBuilder, - ecs::schedule::{InternedScheduleLabel, ScheduleLabel}, + ecs::{ + query::QueryData, + schedule::{InternedScheduleLabel, ScheduleLabel}, + }, prelude::*, }; @@ -69,7 +72,7 @@ where fn build(&self, app: &mut App) { app.add_systems( self.schedule, - (tween_event_system::) + (tween_self_event_system::) .in_set(crate::TweenSystemSet::ApplyTween), ) .add_message::>(); @@ -175,12 +178,11 @@ pub struct TweenEvent { pub entity: Entity, } -/// Fires [`TweenEvent`] with optional user data whenever [`TimeSpanProgress`] +/// Fires [`TweenEvent`] messages with optional user data whenever [`TimeSpanProgress`] /// and [`TweenEventData`] exist in the same entity and data is `Some`, /// cloning the data. #[allow(clippy::type_complexity)] -pub fn tween_event_system( - mut commands: Commands, +pub fn tween_message_system( q_tween_event_data: Query< ( Entity, @@ -188,7 +190,11 @@ pub fn tween_event_system( &TimeSpanProgress, Option<&TweenInterpolationValue>, ), - (Without, With>), + ( + Without, + With>, + With, + ), >, mut event_writer: MessageWriter>, ) where @@ -203,8 +209,79 @@ pub fn tween_event_system( interpolation_value: interpolation_value.map(|v| v.0), entity, }; - commands.trigger(event.clone()); event_writer.write(event); }, ); } + +/// Fires a [`TweenEvent`] to the tweened entity with optional user data +/// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the +/// same entity and data is `Some`, cloning the data. +#[allow(clippy::type_complexity)] +pub fn tween_self_event_system( + mut commands: Commands, + q_tween_event_data: Query< + ( + Entity, + &TweenEventData, + &TimeSpanProgress, + Option<&TweenInterpolationValue>, + ), + ( + Without, + With>, + With, + ), + >, +) where + Data: Clone + Send + Sync + 'static, + TimeCtx: Default + Send + Sync + 'static, +{ + q_tween_event_data.iter().for_each( + |(entity, event_data, progress, interpolation_value)| { + let event = TweenEvent { + data: event_data.0.clone(), + progress: *progress, + interpolation_value: interpolation_value.map(|v| v.0), + entity, + }; + commands.trigger(event.clone()); + }, + ); +} + +/// Fires a [`TweenEvent`] to the animation root with optional user data +/// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the +/// same entity and data is `Some`, cloning the data. +#[allow(clippy::type_complexity)] +pub fn tween_root_event_system( + mut commands: Commands, + q_tween_event_data: Query< + ( + &ChildOf, + &TweenEventData, + &TimeSpanProgress, + Option<&TweenInterpolationValue>, + ), + ( + Without, + With>, + With, + ), + >, +) where + Data: Clone + Send + Sync + 'static, + TimeCtx: Default + Send + Sync + 'static, +{ + q_tween_event_data.iter().for_each( + |(child_of, event_data, progress, interpolation_value)| { + let event = TweenEvent { + data: event_data.0.clone(), + progress: *progress, + interpolation_value: interpolation_value.map(|v| v.0), + entity: child_of.parent(), + }; + commands.trigger(event.clone()); + }, + ); +} From b3d4dee7e5558e594f0deb0c15d050cf8e9e2846 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 19:11:27 +0100 Subject: [PATCH 08/34] tweak: updates combinator names and descriptions These event combinators are now all technically `self_` event combinators, so I've named them as such --- examples/demo/entity_event.rs | 6 +++--- examples/demo/event.rs | 6 +++--- src/combinator/animation_combinators.rs | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/demo/entity_event.rs b/examples/demo/entity_event.rs index 5d62d9b..d449538 100644 --- a/examples/demo/entity_event.rs +++ b/examples/demo/entity_event.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_tween::{ bevy_time_runner::TimeRunnerEnded, - combinator::{event, forward, sequence}, + combinator::{forward, self_event, sequence}, prelude::*, }; @@ -32,9 +32,9 @@ fn setup(mut commands: Commands) { .animation() .insert(sequence(( forward(Duration::from_secs_f32(3.)), - event("tween"), + self_event("tween"), forward(Duration::from_secs_f32(0.5)), - event("event"), + self_event("event"), ))) .observe(|trigger: On>| { println!("TweenEvent on root: {}", trigger.data) diff --git a/examples/demo/event.rs b/examples/demo/event.rs index 4505cee..7070893 100644 --- a/examples/demo/event.rs +++ b/examples/demo/event.rs @@ -65,7 +65,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .animation() .repeat(Repeat::Infinitely) .insert(sequence(( - event("bump"), + self_event("bump"), tween( secs(1.), EaseKind::ExponentialIn, @@ -76,8 +76,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ), ), backward(secs(0.2)), - event_for(secs(0.2), "small_boom"), - event("boom"), + self_event_for(secs(0.2), "small_boom"), + self_event("boom"), tween( secs(1.), EaseKind::CircularOut, diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 584cb7c..4526a01 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -77,7 +77,7 @@ where } } -/// Combinator for creating an tween event. +/// Combinator for creating a tween event on the tween component. /// /// Event will be emitted at current position. /// @@ -88,7 +88,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn event( +pub fn self_event( event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where @@ -103,7 +103,7 @@ where } } -/// Combinator for creating an tween event. +/// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the provided position. /// @@ -114,7 +114,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn event_at( +pub fn self_event_at( at: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) @@ -130,7 +130,7 @@ where } } -/// Combinator for creating an tween event. +/// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the current position for provided length every frame. /// @@ -141,7 +141,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn event_for( +pub fn self_event_for( length: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) @@ -160,7 +160,7 @@ where } } -/// Combinator for creating an tween event. +/// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the provided span every frame. /// @@ -171,7 +171,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn event_exact( +pub fn self_event_exact( span: S, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) From 0d627353a58acf57f2ded1011975e5899385dd61 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 19:18:30 +0100 Subject: [PATCH 09/34] feat: adds new event systems to plugins and exports --- src/lib.rs | 4 +++- src/tween_event.rs | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce937b7..08d321e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -418,7 +418,9 @@ pub use tween::component_tween_system; pub use tween::resource_dyn_tween_system; pub use tween::resource_tween_system; -pub use tween_event::tween_self_event_system; +pub use tween_event::{ + tween_message_system, tween_root_event_system, tween_self_event_system, +}; /// Default plugins for using crate. pub struct DefaultTweenPlugins diff --git a/src/tween_event.rs b/src/tween_event.rs index 97f0b58..66d16a0 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -72,7 +72,11 @@ where fn build(&self, app: &mut App) { app.add_systems( self.schedule, - (tween_self_event_system::) + ( + tween_message_system::, + tween_self_event_system::, + tween_root_event_system::, + ) .in_set(crate::TweenSystemSet::ApplyTween), ) .add_message::>(); From 41a1f6b23c995b699b00db565d2f91df60af294a Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 19:25:15 +0100 Subject: [PATCH 10/34] feat: adds combinators for new event emitter types --- src/combinator/animation_combinators.rs | 231 +++++++++++++++++++++++- 1 file changed, 230 insertions(+), 1 deletion(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 4526a01..5587eb3 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -1,5 +1,10 @@ use super::AnimationCommands; -use crate::{prelude::TweenEventData, tween_event::SelfEventEmittingTween}; +use crate::{ + prelude::TweenEventData, + tween_event::{ + MessageEmittingTween, RootEventEmittingTween, SelfEventEmittingTween, + }, +}; use bevy::prelude::*; use bevy_time_runner::TimeSpan; use std::time::Duration; @@ -77,6 +82,32 @@ where } } +/// Combinator for dispatching a tween event message. +/// +/// Event will be emitted at current position. +/// +/// Position is not mutated because the event has no length. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message( + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + move |a, pos| { + a.spawn(( + TimeSpan::try_from(*pos..=*pos).unwrap(), + TweenEventData::with_data(event_data), + MessageEmittingTween, + )); + } +} + /// Combinator for creating a tween event on the tween component. /// /// Event will be emitted at current position. @@ -103,6 +134,59 @@ where } } +/// Combinator for creating a tween event on the animation root. +/// +/// Event will be emitted at current position. +/// +/// Position is not mutated because the event has no length. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn root_event( + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + move |a, pos| { + a.spawn(( + TimeSpan::try_from(*pos..=*pos).unwrap(), + TweenEventData::with_data(event_data), + RootEventEmittingTween, + )); + } +} + +/// Combinator for dispatching a tween event message. +/// +/// Event will be emitted at the provided position. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message_at( + at: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + move |a, _pos| { + a.spawn(( + TimeSpan::try_from(at..=at).unwrap(), + TweenEventData::with_data(event_data), + MessageEmittingTween, + )); + } +} + /// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the provided position. @@ -130,6 +214,63 @@ where } } +/// Combinator for creating an tween event on the animation root. +/// +/// Event will be emitted at the provided position. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn root_event_at( + at: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + move |a, _pos| { + a.spawn(( + TimeSpan::try_from(at..=at).unwrap(), + TweenEventData::with_data(event_data), + RootEventEmittingTween, + )); + } +} + +/// Combinator for dispatching a tween event message. +/// +/// Event will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message_for( + length: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + move |a, pos| { + let start = *pos; + let end = start + length; + a.spawn(( + TimeSpan::try_from(start..end).unwrap(), + TweenEventData::with_data(event_data), + MessageEmittingTween, + )); + *pos = end; + } +} + /// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the current position for provided length every frame. @@ -160,6 +301,65 @@ where } } +/// Combinator for creating an tween event on the animation root. +/// +/// Event will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn root_event_for( + length: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + move |a, pos| { + let start = *pos; + let end = start + length; + a.spawn(( + TimeSpan::try_from(start..end).unwrap(), + TweenEventData::with_data(event_data), + RootEventEmittingTween, + )); + *pos = end; + } +} + +/// Combinator for dispatching a tween event message. +/// +/// Event will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message_exact( + span: S, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, +{ + move |a, _pos| { + a.spawn(( + span.try_into().unwrap(), + TweenEventData::with_data(event_data), + SelfEventEmittingTween, + )); + } +} + /// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the provided span every frame. @@ -189,6 +389,35 @@ where } } +/// Combinator for creating an tween event on the animation root. +/// +/// Event will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn root_event_exact( + span: S, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, +{ + move |a, _pos| { + a.spawn(( + span.try_into().unwrap(), + TweenEventData::with_data(event_data), + RootEventEmittingTween, + )); + } +} + /// Shift the position forward by provided duration pub fn forward( by: Duration, From 7c9c6d1c723fa0ab688bf63a5f3ed4873e5bc3f3 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 19:34:08 +0100 Subject: [PATCH 11/34] fix: corrects event combinators used in examples --- examples/demo/entity_event.rs | 6 +++--- examples/demo/event.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/demo/entity_event.rs b/examples/demo/entity_event.rs index d449538..6b8fd74 100644 --- a/examples/demo/entity_event.rs +++ b/examples/demo/entity_event.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_tween::{ bevy_time_runner::TimeRunnerEnded, - combinator::{forward, self_event, sequence}, + combinator::{forward, root_event, sequence}, prelude::*, }; @@ -32,9 +32,9 @@ fn setup(mut commands: Commands) { .animation() .insert(sequence(( forward(Duration::from_secs_f32(3.)), - self_event("tween"), + root_event("tween"), forward(Duration::from_secs_f32(0.5)), - self_event("event"), + root_event("event"), ))) .observe(|trigger: On>| { println!("TweenEvent on root: {}", trigger.data) diff --git a/examples/demo/event.rs b/examples/demo/event.rs index 7070893..fa40088 100644 --- a/examples/demo/event.rs +++ b/examples/demo/event.rs @@ -65,7 +65,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .animation() .repeat(Repeat::Infinitely) .insert(sequence(( - self_event("bump"), + message("bump"), tween( secs(1.), EaseKind::ExponentialIn, @@ -76,8 +76,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ), ), backward(secs(0.2)), - self_event_for(secs(0.2), "small_boom"), - self_event("boom"), + message_for(secs(0.2), "small_boom"), + message("boom"), tween( secs(1.), EaseKind::CircularOut, From 67464d422d95f554455a63ec9ac4af4da6a3ed2e Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:16:30 +0100 Subject: [PATCH 12/34] tweak: extracts common dispatch logic --- src/combinator/animation_combinators.rs | 147 +++++++++++++----------- 1 file changed, 81 insertions(+), 66 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 5587eb3..96a86ad 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -99,13 +99,7 @@ pub fn message( where Data: Send + Sync + 'static, { - move |a, pos| { - a.spawn(( - TimeSpan::try_from(*pos..=*pos).unwrap(), - TweenEventData::with_data(event_data), - MessageEmittingTween, - )); - } + dispatch::(event_data) } /// Combinator for creating a tween event on the tween component. @@ -125,13 +119,7 @@ pub fn self_event( where Data: Send + Sync + 'static, { - move |a, pos| { - a.spawn(( - TimeSpan::try_from(*pos..=*pos).unwrap(), - TweenEventData::with_data(event_data), - SelfEventEmittingTween, - )); - } + dispatch::(event_data) } /// Combinator for creating a tween event on the animation root. @@ -150,12 +138,27 @@ pub fn root_event( ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, +{ + dispatch::(event_data) +} + +/// Combinator for dispatching a tween event. +/// +/// `With` informs how the tween event is dispatched. +/// +/// Event will be emitted at current position. +fn dispatch( + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, + With: Default + Bundle, { move |a, pos| { a.spawn(( TimeSpan::try_from(*pos..=*pos).unwrap(), TweenEventData::with_data(event_data), - RootEventEmittingTween, + With::default(), )); } } @@ -178,13 +181,7 @@ pub fn message_at( where Data: Send + Sync + 'static, { - move |a, _pos| { - a.spawn(( - TimeSpan::try_from(at..=at).unwrap(), - TweenEventData::with_data(event_data), - MessageEmittingTween, - )); - } + dispatch_at::(at, event_data) } /// Combinator for creating an tween event on the tween component. @@ -205,13 +202,7 @@ pub fn self_event_at( where Data: Send + Sync + 'static, { - move |a, _pos| { - a.spawn(( - TimeSpan::try_from(at..=at).unwrap(), - TweenEventData::with_data(event_data), - SelfEventEmittingTween, - )); - } + dispatch_at::(at, event_data) } /// Combinator for creating an tween event on the animation root. @@ -231,12 +222,28 @@ pub fn root_event_at( ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, +{ + dispatch_at::(at, event_data) +} + +/// Combinator for dispatching a tween event. +/// +/// `With` informs how the tween event is dispatched. +/// +/// Event will be emitted at the provided position. +fn dispatch_at( + at: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, + With: Default + Bundle, { move |a, _pos| { a.spawn(( TimeSpan::try_from(at..=at).unwrap(), TweenEventData::with_data(event_data), - RootEventEmittingTween, + With::default(), )); } } @@ -259,16 +266,7 @@ pub fn message_for( where Data: Send + Sync + 'static, { - move |a, pos| { - let start = *pos; - let end = start + length; - a.spawn(( - TimeSpan::try_from(start..end).unwrap(), - TweenEventData::with_data(event_data), - MessageEmittingTween, - )); - *pos = end; - } + dispatch_for::(length, event_data) } /// Combinator for creating an tween event on the tween component. @@ -289,16 +287,7 @@ pub fn self_event_for( where Data: Send + Sync + 'static, { - move |a, pos| { - let start = *pos; - let end = start + length; - a.spawn(( - TimeSpan::try_from(start..end).unwrap(), - TweenEventData::with_data(event_data), - SelfEventEmittingTween, - )); - *pos = end; - } + dispatch_for::(length, event_data) } /// Combinator for creating an tween event on the animation root. @@ -318,6 +307,24 @@ pub fn root_event_for( ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, +{ + dispatch_for::(length, event_data) +} + +/// Combinator for dispatching a tween event. +/// +/// `With` informs how the tween event is dispatched. +/// +/// Event will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +fn dispatch_for( + length: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, + With: Default + Bundle, { move |a, pos| { let start = *pos; @@ -325,7 +332,7 @@ where a.spawn(( TimeSpan::try_from(start..end).unwrap(), TweenEventData::with_data(event_data), - RootEventEmittingTween, + With::default(), )); *pos = end; } @@ -351,13 +358,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - move |a, _pos| { - a.spawn(( - span.try_into().unwrap(), - TweenEventData::with_data(event_data), - SelfEventEmittingTween, - )); - } + dispatch_exact::(span, event_data) } /// Combinator for creating an tween event on the tween component. @@ -380,13 +381,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - move |a, _pos| { - a.spawn(( - span.try_into().unwrap(), - TweenEventData::with_data(event_data), - SelfEventEmittingTween, - )); - } + dispatch_exact::(span, event_data) } /// Combinator for creating an tween event on the animation root. @@ -408,12 +403,32 @@ where S: TryInto, S::Error: std::fmt::Debug, Data: Send + Sync + 'static, +{ + dispatch_exact::(span, event_data) +} + +/// Combinator for dispatching a tween event. +/// +/// `With` informs how the tween event is dispatched. +/// +/// Event will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +pub fn dispatch_exact( + span: S, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, + With: Default + Bundle, { move |a, _pos| { a.spawn(( span.try_into().unwrap(), TweenEventData::with_data(event_data), - RootEventEmittingTween, + With::default(), )); } } From b31fe049b714464c90a6f82080702214b6ed85b4 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:20:05 +0100 Subject: [PATCH 13/34] fix: corrects marker component used for dispatching root events --- src/tween_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 66d16a0..6381b2a 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -270,7 +270,7 @@ pub fn tween_root_event_system( ( Without, With>, - With, + With, ), >, ) where From 497c427c3cacc7b74bc2d545606276670ce07367 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:25:20 +0100 Subject: [PATCH 14/34] docs: updates function comments to avoid confusion... Between tween events and tween messages. There might still be a little confusion as the same `TweenEvent` struct is used for both... Not quite sure what to do about this one. --- src/combinator/animation_combinators.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 96a86ad..d36e27a 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -82,9 +82,9 @@ where } } -/// Combinator for dispatching a tween event message. +/// Combinator for dispatching a tween message. /// -/// Event will be emitted at current position. +/// Message will be emitted at current position. /// /// Position is not mutated because the event has no length. /// @@ -163,9 +163,9 @@ where } } -/// Combinator for dispatching a tween event message. +/// Combinator for dispatching a tween message. /// -/// Event will be emitted at the provided position. +/// Message will be emitted at the provided position. /// /// Position is not mutated because the operation is not relative. /// @@ -248,9 +248,9 @@ where } } -/// Combinator for dispatching a tween event message. +/// Combinator for dispatching a tween message. /// -/// Event will be emitted at the current position for provided length every frame. +/// Message will be emitted at the current position for provided length every frame. /// /// Position is at the end of the event. /// @@ -338,9 +338,9 @@ where } } -/// Combinator for dispatching a tween event message. +/// Combinator for dispatching a tween message. /// -/// Event will be emitted at the provided span every frame. +/// Message will be emitted at the provided span every frame. /// /// Position is not mutated because the operation is not relative. /// From d5cb889cdc66b8d642d884485510e3c49522bbc1 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:29:26 +0100 Subject: [PATCH 15/34] feat: adds cover all combinator for dispatching data --- src/combinator/animation_combinators.rs | 91 +++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index d36e27a..565b605 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -82,6 +82,26 @@ where } } +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at current position. +/// +/// Position is not mutated because the event has no length. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send( + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch::(event_data) +} + /// Combinator for dispatching a tween message. /// /// Message will be emitted at current position. @@ -163,6 +183,29 @@ where } } +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at the provided position. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send_at( + at: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_at::( + at, event_data, + ) +} + /// Combinator for dispatching a tween message. /// /// Message will be emitted at the provided position. @@ -248,6 +291,29 @@ where } } +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send_for( + length: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_for::( + length, event_data, + ) +} + /// Combinator for dispatching a tween message. /// /// Message will be emitted at the current position for provided length every frame. @@ -338,6 +404,31 @@ where } } +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send_exact( + span: S, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, +{ + dispatch_exact::( + span, event_data, + ) +} + /// Combinator for dispatching a tween message. /// /// Message will be emitted at the provided span every frame. From e9f05ac344d4d41198f4b8680591980b6544af61 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:45:41 +0100 Subject: [PATCH 16/34] tweak: extracts common event creation logic --- src/tween_event.rs | 101 ++++++++++++++++++++++----------------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 6381b2a..4cfeb14 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -182,18 +182,51 @@ pub struct TweenEvent { pub entity: Entity, } +/// QueryData used to construct a [`TweenEvent`] for a given [`TweenEventData`] +#[derive(QueryData)] +pub struct TweenSystemData { + /// The tween entity the event combinator created + pub entity: Entity, + /// The data passed to the event combinator + pub event_data: &'static TweenEventData, + /// The progress for the event + pub progress: &'static TimeSpanProgress, + /// The current interpolation value + pub interpolation_value: Option<&'static TweenInterpolationValue>, +} + +// Note we need to implement event for the data item, +// due to how the [`QueryData`] derive is implemented. +impl<'w, 's, Data> TweenSystemDataItem<'w, 's, Data> +where + Data: Clone + Send + Sync + 'static, +{ + /// Constructs a tween event for the given tween data entity + pub fn event(&self) -> TweenEvent { + TweenEvent { + data: self.event_data.0.clone(), + progress: *self.progress, + interpolation_value: self.interpolation_value.map(|v| v.0), + entity: self.entity, + } + } + + /// Constructs a tween event for a different tween data entity + pub fn event_on(&self, entity: Entity) -> TweenEvent { + TweenEvent { + entity, + ..self.event() + } + } +} + /// Fires [`TweenEvent`] messages with optional user data whenever [`TimeSpanProgress`] /// and [`TweenEventData`] exist in the same entity and data is `Some`, /// cloning the data. #[allow(clippy::type_complexity)] pub fn tween_message_system( q_tween_event_data: Query< - ( - Entity, - &TweenEventData, - &TimeSpanProgress, - Option<&TweenInterpolationValue>, - ), + TweenSystemData, ( Without, With>, @@ -205,17 +238,9 @@ pub fn tween_message_system( Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { - q_tween_event_data.iter().for_each( - |(entity, event_data, progress, interpolation_value)| { - let event = TweenEvent { - data: event_data.0.clone(), - progress: *progress, - interpolation_value: interpolation_value.map(|v| v.0), - entity, - }; - event_writer.write(event); - }, - ); + q_tween_event_data.iter().for_each(|data| { + event_writer.write(data.event()); + }); } /// Fires a [`TweenEvent`] to the tweened entity with optional user data @@ -225,12 +250,7 @@ pub fn tween_message_system( pub fn tween_self_event_system( mut commands: Commands, q_tween_event_data: Query< - ( - Entity, - &TweenEventData, - &TimeSpanProgress, - Option<&TweenInterpolationValue>, - ), + TweenSystemData, ( Without, With>, @@ -241,17 +261,9 @@ pub fn tween_self_event_system( Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { - q_tween_event_data.iter().for_each( - |(entity, event_data, progress, interpolation_value)| { - let event = TweenEvent { - data: event_data.0.clone(), - progress: *progress, - interpolation_value: interpolation_value.map(|v| v.0), - entity, - }; - commands.trigger(event.clone()); - }, - ); + q_tween_event_data.iter().for_each(|data| { + commands.trigger(data.event()); + }); } /// Fires a [`TweenEvent`] to the animation root with optional user data @@ -261,12 +273,7 @@ pub fn tween_self_event_system( pub fn tween_root_event_system( mut commands: Commands, q_tween_event_data: Query< - ( - &ChildOf, - &TweenEventData, - &TimeSpanProgress, - Option<&TweenInterpolationValue>, - ), + (&ChildOf, TweenSystemData), ( Without, With>, @@ -277,15 +284,7 @@ pub fn tween_root_event_system( Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { - q_tween_event_data.iter().for_each( - |(child_of, event_data, progress, interpolation_value)| { - let event = TweenEvent { - data: event_data.0.clone(), - progress: *progress, - interpolation_value: interpolation_value.map(|v| v.0), - entity: child_of.parent(), - }; - commands.trigger(event.clone()); - }, - ); + q_tween_event_data.iter().for_each(|(child_of, data)| { + commands.trigger(data.event_on(child_of.parent())); + }); } From 5ae474123fb82cd520d6285c4d2b4ea777eb170f Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:46:48 +0100 Subject: [PATCH 17/34] docs: formats doc comment --- src/tween_event.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 4cfeb14..f34b57f 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -220,9 +220,9 @@ where } } -/// Fires [`TweenEvent`] messages with optional user data whenever [`TimeSpanProgress`] -/// and [`TweenEventData`] exist in the same entity and data is `Some`, -/// cloning the data. +/// Fires a [`TweenEvent`] message with optional user data +/// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the +/// same entity and data is `Some`, cloning the data. #[allow(clippy::type_complexity)] pub fn tween_message_system( q_tween_event_data: Query< From 56e3100be7b8680f1d9c3ea36da6cfc68a017f91 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 20:51:55 +0100 Subject: [PATCH 18/34] tweak: renames examples These better match the functionality they are now demonstrating and actually match the combinator names used within the examples --- Cargo.toml | 8 ++++---- README.md | 2 +- examples/demo/{entity_event.rs => events.rs} | 0 examples/demo/{event.rs => message.rs} | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename examples/demo/{entity_event.rs => events.rs} (100%) rename examples/demo/{event.rs => message.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 9b62720..224cc4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,13 +113,13 @@ path = "examples/demo/hold.rs" required-features = ["bevy_sprite", "bevy_asset"] [[example]] -name = "event" -path = "examples/demo/event.rs" +name = "message" +path = "examples/demo/message.rs" required-features = ["bevy_sprite", "bevy_asset"] [[example]] -name = "entity_event" -path = "examples/demo/entity_event.rs" +name = "events" +path = "examples/demo/events.rs" [[example]] name = "sprite_sheet" diff --git a/README.md b/README.md index f0dfd3c..ff35143 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ https://github.com/Multirious/bevy_tween/assets/77918086/33a297a6-19f2-4146-a906 --- -`cargo run --example event -F bevy/bevy_winit`
+`cargo run --example message -F bevy/bevy_winit`
Showcasing the tween event feature.
https://github.com/Multirious/bevy_tween/assets/77918086/9507c467-6428-4aed-bd00-511f05e6e951 diff --git a/examples/demo/entity_event.rs b/examples/demo/events.rs similarity index 100% rename from examples/demo/entity_event.rs rename to examples/demo/events.rs diff --git a/examples/demo/event.rs b/examples/demo/message.rs similarity index 100% rename from examples/demo/event.rs rename to examples/demo/message.rs From ae4bd26acd4eac0a128867f9bc13ab7a1b5c6b2e Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 21:02:09 +0100 Subject: [PATCH 19/34] docs: updates module doc comments for tween events --- src/tween_event.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index f34b57f..4efc060 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -10,12 +10,14 @@ //! - [`TweenEventData`] //! //! **Systems** -//! - [`tween_event_system`] +//! - [`tween_message_system`] +//! - [`tween_self_event_system`] +//! - [`tween_root_event_system`] //! //! **Events**: //! - [`TweenEvent`] //! -//! Simple event system that fires generic data in a timed manner. +//! Simple event systems that fires generic data in a timed manner. //! Your data is stored in [`TweenEventData`] and the event type is [`TweenEvent`]. //! Tween event is not necessarily related to tweening. //! From 52fc9366f45e1bfbdafaf6f56de253f4db401d05 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 21:05:47 +0100 Subject: [PATCH 20/34] docs: updates doc comments --- src/tween_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 4efc060..1579f08 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -146,7 +146,7 @@ where #[reflect(Component)] pub struct SelfEventEmittingTween; -/// Used to mark message-emitting tweens (tweens that emit a message with `message.data: Data`) +/// Used to mark message-emitting tweens (tweens that emit a tween event message) #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] pub struct MessageEmittingTween; From da2469ae36dfb29cbbe26215d17d96a947df5874 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 21:11:49 +0100 Subject: [PATCH 21/34] docs: updates doc comments --- src/combinator/animation_combinators.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 565b605..5720437 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -142,7 +142,7 @@ where dispatch::(event_data) } -/// Combinator for creating a tween event on the animation root. +/// Combinator for creating a tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at current position. /// @@ -248,7 +248,7 @@ where dispatch_at::(at, event_data) } -/// Combinator for creating an tween event on the animation root. +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the provided position. /// @@ -356,7 +356,7 @@ where dispatch_for::(length, event_data) } -/// Combinator for creating an tween event on the animation root. +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the current position for provided length every frame. /// @@ -475,7 +475,7 @@ where dispatch_exact::(span, event_data) } -/// Combinator for creating an tween event on the animation root. +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the provided span every frame. /// From 9dbc0ad85be5b01aedb695d3d7bf8048cff1a5b2 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 21:21:12 +0100 Subject: [PATCH 22/34] docs: corrects system links --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 08d321e..63fbc56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -606,7 +606,9 @@ pub enum TweenSystemSet { /// - [`tween::asset_tween_system`] /// /// Events is not necessary related to tweening but their code is still working in the same area. - /// - [`tween_event::tween_event_system`] + /// - [`tween_event::tween_message_system`] + /// - [`tween_event::tween_self_event_system`] + /// - [`tween_event::tween_root_event_system`] ApplyTween, } From 6217758b24194efd9431647f89d1116ae092f458 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 21:22:19 +0100 Subject: [PATCH 23/34] docs: corrects system links --- src/tween_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 1579f08..1349d13 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -171,7 +171,7 @@ impl TweenEventData<()> { } /// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity -/// by [`tween_event_system`]. +/// by [`tween_message_system`], [`tween_self_event_system`] and [`tween_root_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] pub struct TweenEvent { /// Custom user data From 05cf1228b76064eb99746391e27c19a948368d7d Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sat, 24 Oct 2026 21:43:17 +0100 Subject: [PATCH 24/34] tweak: reorders event types for consistency --- src/combinator/animation_combinators.rs | 48 ++++++++++++------------- src/tween_event.rs | 34 +++++++++--------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 5720437..c952ff0 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -122,7 +122,7 @@ where dispatch::(event_data) } -/// Combinator for creating a tween event on the tween component. +/// Combinator for creating a tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at current position. /// @@ -133,16 +133,16 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn self_event( +pub fn root_event( event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch::(event_data) } -/// Combinator for creating a tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. +/// Combinator for creating a tween event on the tween component. /// /// Event will be emitted at current position. /// @@ -153,13 +153,13 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event( +pub fn self_event( event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch::(event_data) } /// Combinator for dispatching a tween event. @@ -227,7 +227,7 @@ where dispatch_at::(at, event_data) } -/// Combinator for creating an tween event on the tween component. +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the provided position. /// @@ -238,17 +238,17 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn self_event_at( +pub fn root_event_at( at: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch_at::(at, event_data) + dispatch_at::(at, event_data) } -/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. +/// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the provided position. /// @@ -259,14 +259,14 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event_at( +pub fn self_event_at( at: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch_at::(at, event_data) + dispatch_at::(at, event_data) } /// Combinator for dispatching a tween event. @@ -335,7 +335,7 @@ where dispatch_for::(length, event_data) } -/// Combinator for creating an tween event on the tween component. +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the current position for provided length every frame. /// @@ -346,17 +346,17 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn self_event_for( +pub fn root_event_for( length: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch_for::(length, event_data) + dispatch_for::(length, event_data) } -/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. +/// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the current position for provided length every frame. /// @@ -367,14 +367,14 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event_for( +pub fn self_event_for( length: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch_for::(length, event_data) + dispatch_for::(length, event_data) } /// Combinator for dispatching a tween event. @@ -452,7 +452,7 @@ where dispatch_exact::(span, event_data) } -/// Combinator for creating an tween event on the tween component. +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the provided span every frame. /// @@ -463,7 +463,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn self_event_exact( +pub fn root_event_exact( span: S, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) @@ -472,10 +472,10 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::(span, event_data) + dispatch_exact::(span, event_data) } -/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. +/// Combinator for creating an tween event on the tween component. /// /// Event will be emitted at the provided span every frame. /// @@ -486,7 +486,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event_exact( +pub fn self_event_exact( span: S, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) @@ -495,7 +495,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::(span, event_data) + dispatch_exact::(span, event_data) } /// Combinator for dispatching a tween event. diff --git a/src/tween_event.rs b/src/tween_event.rs index 1349d13..3343ef2 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -141,11 +141,6 @@ pub struct TweenEventData(pub Data) where Data: Send + Sync + 'static; -/// Used to mark self event-emitting tween (tweens that emit an entity event to the tweening component) -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] -#[reflect(Component)] -pub struct SelfEventEmittingTween; - /// Used to mark message-emitting tweens (tweens that emit a tween event message) #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] @@ -156,6 +151,11 @@ pub struct MessageEmittingTween; #[reflect(Component)] pub struct RootEventEmittingTween; +/// Used to mark self event-emitting tween (tweens that emit an entity event to the tweening component) +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] +#[reflect(Component)] +pub struct SelfEventEmittingTween; + impl TweenEventData { /// Create new [`TweenEventData`] with custom user data. pub fn with_data(data: Data) -> Self { @@ -245,48 +245,48 @@ pub fn tween_message_system( }); } -/// Fires a [`TweenEvent`] to the tweened entity with optional user data +/// Fires a [`TweenEvent`] to the animation root with optional user data /// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the /// same entity and data is `Some`, cloning the data. #[allow(clippy::type_complexity)] -pub fn tween_self_event_system( +pub fn tween_root_event_system( mut commands: Commands, q_tween_event_data: Query< - TweenSystemData, + (&ChildOf, TweenSystemData), ( Without, With>, - With, + With, ), >, ) where Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { - q_tween_event_data.iter().for_each(|data| { - commands.trigger(data.event()); + q_tween_event_data.iter().for_each(|(child_of, data)| { + commands.trigger(data.event_on(child_of.parent())); }); } -/// Fires a [`TweenEvent`] to the animation root with optional user data +/// Fires a [`TweenEvent`] to the tweened entity with optional user data /// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the /// same entity and data is `Some`, cloning the data. #[allow(clippy::type_complexity)] -pub fn tween_root_event_system( +pub fn tween_self_event_system( mut commands: Commands, q_tween_event_data: Query< - (&ChildOf, TweenSystemData), + TweenSystemData, ( Without, With>, - With, + With, ), >, ) where Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { - q_tween_event_data.iter().for_each(|(child_of, data)| { - commands.trigger(data.event_on(child_of.parent())); + q_tween_event_data.iter().for_each(|data| { + commands.trigger(data.event()); }); } From 8034ae44bf6784d3f9e5b5a958e08ef27f1e2e55 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 09:20:17 +0000 Subject: [PATCH 25/34] tweak: removes `self` event combinators The way these combinators send events is a difficult to observe with entity events, so I'm discarding them in favor of `root` event combinators --- src/combinator/animation_combinators.rs | 85 ------------------------- 1 file changed, 85 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index c952ff0..75615f2 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -142,26 +142,6 @@ where dispatch::(event_data) } -/// Combinator for creating a tween event on the tween component. -/// -/// Event will be emitted at current position. -/// -/// Position is not mutated because the event has no length. -/// -///
-/// -/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). -/// -///
-pub fn self_event( - event_data: Data, -) -> impl FnOnce(&mut AnimationCommands, &mut Duration) -where - Data: Send + Sync + 'static, -{ - dispatch::(event_data) -} - /// Combinator for dispatching a tween event. /// /// `With` informs how the tween event is dispatched. @@ -248,27 +228,6 @@ where dispatch_at::(at, event_data) } -/// Combinator for creating an tween event on the tween component. -/// -/// Event will be emitted at the provided position. -/// -/// Position is not mutated because the operation is not relative. -/// -///
-/// -/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). -/// -///
-pub fn self_event_at( - at: Duration, - event_data: Data, -) -> impl FnOnce(&mut AnimationCommands, &mut Duration) -where - Data: Send + Sync + 'static, -{ - dispatch_at::(at, event_data) -} - /// Combinator for dispatching a tween event. /// /// `With` informs how the tween event is dispatched. @@ -356,27 +315,6 @@ where dispatch_for::(length, event_data) } -/// Combinator for creating an tween event on the tween component. -/// -/// Event will be emitted at the current position for provided length every frame. -/// -/// Position is at the end of the event. -/// -///
-/// -/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). -/// -///
-pub fn self_event_for( - length: Duration, - event_data: Data, -) -> impl FnOnce(&mut AnimationCommands, &mut Duration) -where - Data: Send + Sync + 'static, -{ - dispatch_for::(length, event_data) -} - /// Combinator for dispatching a tween event. /// /// `With` informs how the tween event is dispatched. @@ -475,29 +413,6 @@ where dispatch_exact::(span, event_data) } -/// Combinator for creating an tween event on the tween component. -/// -/// Event will be emitted at the provided span every frame. -/// -/// Position is not mutated because the operation is not relative. -/// -///
-/// -/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). -/// -///
-pub fn self_event_exact( - span: S, - event_data: Data, -) -> impl FnOnce(&mut AnimationCommands, &mut Duration) -where - S: TryInto, - S::Error: std::fmt::Debug, - Data: Send + Sync + 'static, -{ - dispatch_exact::(span, event_data) -} - /// Combinator for dispatching a tween event. /// /// `With` informs how the tween event is dispatched. From abcf0234e65bf91fcafb55e93a9e1d90a84aac9d Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 09:22:39 +0000 Subject: [PATCH 26/34] tweak: removes other self event handler logic --- src/combinator/animation_combinators.rs | 4 +--- src/lib.rs | 4 +--- src/tween_event.rs | 32 +------------------------ 3 files changed, 3 insertions(+), 37 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 75615f2..4a94605 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -1,9 +1,7 @@ use super::AnimationCommands; use crate::{ prelude::TweenEventData, - tween_event::{ - MessageEmittingTween, RootEventEmittingTween, SelfEventEmittingTween, - }, + tween_event::{MessageEmittingTween, RootEventEmittingTween}, }; use bevy::prelude::*; use bevy_time_runner::TimeSpan; diff --git a/src/lib.rs b/src/lib.rs index 63fbc56..ad27088 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -418,9 +418,7 @@ pub use tween::component_tween_system; pub use tween::resource_dyn_tween_system; pub use tween::resource_tween_system; -pub use tween_event::{ - tween_message_system, tween_root_event_system, tween_self_event_system, -}; +pub use tween_event::{tween_message_system, tween_root_event_system}; /// Default plugins for using crate. pub struct DefaultTweenPlugins diff --git a/src/tween_event.rs b/src/tween_event.rs index 3343ef2..01e0f55 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -11,7 +11,6 @@ //! //! **Systems** //! - [`tween_message_system`] -//! - [`tween_self_event_system`] //! - [`tween_root_event_system`] //! //! **Events**: @@ -76,7 +75,6 @@ where self.schedule, ( tween_message_system::, - tween_self_event_system::, tween_root_event_system::, ) .in_set(crate::TweenSystemSet::ApplyTween), @@ -151,11 +149,6 @@ pub struct MessageEmittingTween; #[reflect(Component)] pub struct RootEventEmittingTween; -/// Used to mark self event-emitting tween (tweens that emit an entity event to the tweening component) -#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] -#[reflect(Component)] -pub struct SelfEventEmittingTween; - impl TweenEventData { /// Create new [`TweenEventData`] with custom user data. pub fn with_data(data: Data) -> Self { @@ -171,7 +164,7 @@ impl TweenEventData<()> { } /// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity -/// by [`tween_message_system`], [`tween_self_event_system`] and [`tween_root_event_system`]. +/// by [`tween_message_system`] and [`tween_root_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] pub struct TweenEvent { /// Custom user data @@ -267,26 +260,3 @@ pub fn tween_root_event_system( commands.trigger(data.event_on(child_of.parent())); }); } - -/// Fires a [`TweenEvent`] to the tweened entity with optional user data -/// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the -/// same entity and data is `Some`, cloning the data. -#[allow(clippy::type_complexity)] -pub fn tween_self_event_system( - mut commands: Commands, - q_tween_event_data: Query< - TweenSystemData, - ( - Without, - With>, - With, - ), - >, -) where - Data: Clone + Send + Sync + 'static, - TimeCtx: Default + Send + Sync + 'static, -{ - q_tween_event_data.iter().for_each(|data| { - commands.trigger(data.event()); - }); -} From 0a460d08944151fb00f19ff984bc164ab9654d89 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 09:25:20 +0000 Subject: [PATCH 27/34] tweak: renames `root_event` -> `event` Also cleans up some left over references to `self_event`s --- examples/demo/events.rs | 6 +++--- src/combinator/animation_combinators.rs | 26 ++++++++++++------------- src/lib.rs | 5 ++--- src/tween_event.rs | 12 ++++++------ 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/examples/demo/events.rs b/examples/demo/events.rs index 6b8fd74..5d62d9b 100644 --- a/examples/demo/events.rs +++ b/examples/demo/events.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use bevy_tween::{ bevy_time_runner::TimeRunnerEnded, - combinator::{forward, root_event, sequence}, + combinator::{event, forward, sequence}, prelude::*, }; @@ -32,9 +32,9 @@ fn setup(mut commands: Commands) { .animation() .insert(sequence(( forward(Duration::from_secs_f32(3.)), - root_event("tween"), + event("tween"), forward(Duration::from_secs_f32(0.5)), - root_event("event"), + event("event"), ))) .observe(|trigger: On>| { println!("TweenEvent on root: {}", trigger.data) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 4a94605..8f40edf 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -1,7 +1,7 @@ use super::AnimationCommands; use crate::{ prelude::TweenEventData, - tween_event::{MessageEmittingTween, RootEventEmittingTween}, + tween_event::{EventEmittingTween, MessageEmittingTween}, }; use bevy::prelude::*; use bevy_time_runner::TimeSpan; @@ -97,7 +97,7 @@ pub fn send( where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch::(event_data) } /// Combinator for dispatching a tween message. @@ -131,13 +131,13 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event( +pub fn event( event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch::(event_data) } /// Combinator for dispatching a tween event. @@ -179,7 +179,7 @@ pub fn send_at( where Data: Send + Sync + 'static, { - dispatch_at::( + dispatch_at::( at, event_data, ) } @@ -216,14 +216,14 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event_at( +pub fn event_at( at: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch_at::(at, event_data) + dispatch_at::(at, event_data) } /// Combinator for dispatching a tween event. @@ -266,7 +266,7 @@ pub fn send_for( where Data: Send + Sync + 'static, { - dispatch_for::( + dispatch_for::( length, event_data, ) } @@ -303,14 +303,14 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event_for( +pub fn event_for( length: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { - dispatch_for::(length, event_data) + dispatch_for::(length, event_data) } /// Combinator for dispatching a tween event. @@ -360,7 +360,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::( + dispatch_exact::( span, event_data, ) } @@ -399,7 +399,7 @@ where /// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). /// /// -pub fn root_event_exact( +pub fn event_exact( span: S, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) @@ -408,7 +408,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::(span, event_data) + dispatch_exact::(span, event_data) } /// Combinator for dispatching a tween event. diff --git a/src/lib.rs b/src/lib.rs index ad27088..ab7bf36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -418,7 +418,7 @@ pub use tween::component_tween_system; pub use tween::resource_dyn_tween_system; pub use tween::resource_tween_system; -pub use tween_event::{tween_message_system, tween_root_event_system}; +pub use tween_event::{tween_event_system, tween_message_system}; /// Default plugins for using crate. pub struct DefaultTweenPlugins @@ -605,8 +605,7 @@ pub enum TweenSystemSet { /// /// Events is not necessary related to tweening but their code is still working in the same area. /// - [`tween_event::tween_message_system`] - /// - [`tween_event::tween_self_event_system`] - /// - [`tween_event::tween_root_event_system`] + /// - [`tween_event::tween_event_system`] ApplyTween, } diff --git a/src/tween_event.rs b/src/tween_event.rs index 01e0f55..a443eda 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -11,7 +11,7 @@ //! //! **Systems** //! - [`tween_message_system`] -//! - [`tween_root_event_system`] +//! - [`tween_event_system`] //! //! **Events**: //! - [`TweenEvent`] @@ -75,7 +75,7 @@ where self.schedule, ( tween_message_system::, - tween_root_event_system::, + tween_event_system::, ) .in_set(crate::TweenSystemSet::ApplyTween), ) @@ -147,7 +147,7 @@ pub struct MessageEmittingTween; /// Used to mark root event-emitting tween (tweens that emit an entity event to the animation root) #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] -pub struct RootEventEmittingTween; +pub struct EventEmittingTween; impl TweenEventData { /// Create new [`TweenEventData`] with custom user data. @@ -164,7 +164,7 @@ impl TweenEventData<()> { } /// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity -/// by [`tween_message_system`] and [`tween_root_event_system`]. +/// by [`tween_message_system`] and [`tween_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] pub struct TweenEvent { /// Custom user data @@ -242,14 +242,14 @@ pub fn tween_message_system( /// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the /// same entity and data is `Some`, cloning the data. #[allow(clippy::type_complexity)] -pub fn tween_root_event_system( +pub fn tween_event_system( mut commands: Commands, q_tween_event_data: Query< (&ChildOf, TweenSystemData), ( Without, With>, - With, + With, ), >, ) where From d6ce9fc04ae13a7fd4d46d401fcfbdc6fe65ba04 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 09:33:51 +0000 Subject: [PATCH 28/34] tweak: prefers additional param for dispatch method This makes it a little easier to understand how dispatch works and also allows for potentially naughty stuff like attaching an dispatch marker and an observer component at the same time. --- src/combinator/animation_combinators.rs | 64 ++++++++++++------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 8f40edf..735fad1 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -97,7 +97,7 @@ pub fn send( where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch(event_data, (MessageEmittingTween, EventEmittingTween)) } /// Combinator for dispatching a tween message. @@ -117,7 +117,7 @@ pub fn message( where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch(event_data, MessageEmittingTween) } /// Combinator for creating a tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. @@ -137,26 +137,26 @@ pub fn event( where Data: Send + Sync + 'static, { - dispatch::(event_data) + dispatch(event_data, EventEmittingTween) } /// Combinator for dispatching a tween event. /// -/// `With` informs how the tween event is dispatched. +/// `with` informs how the tween event is dispatched. /// /// Event will be emitted at current position. -fn dispatch( +fn dispatch( event_data: Data, + with: impl Bundle, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, - With: Default + Bundle, { move |a, pos| { a.spawn(( TimeSpan::try_from(*pos..=*pos).unwrap(), TweenEventData::with_data(event_data), - With::default(), + with, )); } } @@ -179,9 +179,7 @@ pub fn send_at( where Data: Send + Sync + 'static, { - dispatch_at::( - at, event_data, - ) + dispatch_at(at, event_data, (MessageEmittingTween, EventEmittingTween)) } /// Combinator for dispatching a tween message. @@ -202,7 +200,7 @@ pub fn message_at( where Data: Send + Sync + 'static, { - dispatch_at::(at, event_data) + dispatch_at(at, event_data, MessageEmittingTween) } /// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. @@ -223,27 +221,27 @@ pub fn event_at( where Data: Send + Sync + 'static, { - dispatch_at::(at, event_data) + dispatch_at(at, event_data, EventEmittingTween) } /// Combinator for dispatching a tween event. /// -/// `With` informs how the tween event is dispatched. +/// `with` informs how the tween event is dispatched. /// /// Event will be emitted at the provided position. -fn dispatch_at( +fn dispatch_at( at: Duration, event_data: Data, + with: impl Bundle, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, - With: Default + Bundle, { move |a, _pos| { a.spawn(( TimeSpan::try_from(at..=at).unwrap(), TweenEventData::with_data(event_data), - With::default(), + with, )); } } @@ -266,8 +264,10 @@ pub fn send_for( where Data: Send + Sync + 'static, { - dispatch_for::( - length, event_data, + dispatch_for( + length, + event_data, + (MessageEmittingTween, EventEmittingTween), ) } @@ -289,7 +289,7 @@ pub fn message_for( where Data: Send + Sync + 'static, { - dispatch_for::(length, event_data) + dispatch_for(length, event_data, MessageEmittingTween) } /// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. @@ -310,23 +310,23 @@ pub fn event_for( where Data: Send + Sync + 'static, { - dispatch_for::(length, event_data) + dispatch_for(length, event_data, EventEmittingTween) } /// Combinator for dispatching a tween event. /// -/// `With` informs how the tween event is dispatched. +/// `with` informs how the tween event is dispatched. /// /// Event will be emitted at the current position for provided length every frame. /// /// Position is at the end of the event. -fn dispatch_for( +fn dispatch_for( length: Duration, event_data: Data, + with: impl Bundle, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, - With: Default + Bundle, { move |a, pos| { let start = *pos; @@ -334,7 +334,7 @@ where a.spawn(( TimeSpan::try_from(start..end).unwrap(), TweenEventData::with_data(event_data), - With::default(), + with, )); *pos = end; } @@ -360,9 +360,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::( - span, event_data, - ) + dispatch_exact(span, event_data, (MessageEmittingTween, EventEmittingTween)) } /// Combinator for dispatching a tween message. @@ -385,7 +383,7 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::(span, event_data) + dispatch_exact(span, event_data, MessageEmittingTween) } /// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. @@ -408,31 +406,31 @@ where S::Error: std::fmt::Debug, Data: Send + Sync + 'static, { - dispatch_exact::(span, event_data) + dispatch_exact(span, event_data, EventEmittingTween) } /// Combinator for dispatching a tween event. /// -/// `With` informs how the tween event is dispatched. +/// `with` informs how the tween event is dispatched. /// /// Event will be emitted at the provided span every frame. /// /// Position is not mutated because the operation is not relative. -pub fn dispatch_exact( +pub fn dispatch_exact( span: S, event_data: Data, + with: impl Bundle, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where S: TryInto, S::Error: std::fmt::Debug, Data: Send + Sync + 'static, - With: Default + Bundle, { move |a, _pos| { a.spawn(( span.try_into().unwrap(), TweenEventData::with_data(event_data), - With::default(), + with, )); } } From 97086dbd645cb90480a274e9232dbebe3da3a183 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 10:08:51 +0000 Subject: [PATCH 29/34] Update src/tween_event.rs Co-authored-by: Multirious <77918086+Multirious@users.noreply.github.com> --- src/tween_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index a443eda..02af3d8 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -163,7 +163,7 @@ impl TweenEventData<()> { } } -/// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity +/// Fires whenever [`TimeSpanProgress`], [`TweenEventData`], and [`MessageEmittingTween`]/[`EventEmittingTween`] exist in the same entity /// by [`tween_message_system`] and [`tween_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] pub struct TweenEvent { From a737a62d94f83b42a5e2afe8f41e388a0ce68886 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 10:16:25 +0000 Subject: [PATCH 30/34] tweak: makes internals of `TweenSystemData` private Fundamentally all that matters about this query data *is* that it can produce a `TweenEvent` for a given entity, not *how* it does that. --- src/tween_event.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 02af3d8..7c04ff3 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -181,13 +181,13 @@ pub struct TweenEvent { #[derive(QueryData)] pub struct TweenSystemData { /// The tween entity the event combinator created - pub entity: Entity, + entity: Entity, /// The data passed to the event combinator - pub event_data: &'static TweenEventData, + event_data: &'static TweenEventData, /// The progress for the event - pub progress: &'static TimeSpanProgress, + progress: &'static TimeSpanProgress, /// The current interpolation value - pub interpolation_value: Option<&'static TweenInterpolationValue>, + interpolation_value: Option<&'static TweenInterpolationValue>, } // Note we need to implement event for the data item, From 1224a357313b38d4796b1b8ab81e4c9c4755f074 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 10:45:40 +0000 Subject: [PATCH 31/34] tweak: handles animation events without a root. --- src/tween_event.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 7c04ff3..c422ccc 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -26,8 +26,10 @@ //! See [`DefaultTweenEventPlugins`] for default events which is also added in //! [`DefaultTweenPlugins`](crate::DefaultTweenPlugins) -use bevy_time_runner::TimeContext; +use bevy_time_runner::{TimeContext, TimeRunner}; +use std::fmt::Debug; use std::marker::PhantomData; +use tracing::warn; use bevy::{ app::PluginGroupBuilder, @@ -252,11 +254,22 @@ pub fn tween_event_system( With, ), >, + q_tween_roots: Query<(), With>, ) where - Data: Clone + Send + Sync + 'static, + Data: Debug + Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { q_tween_event_data.iter().for_each(|(child_of, data)| { - commands.trigger(data.event_on(child_of.parent())); + let root = child_of.parent(); + if !q_tween_roots.contains(root) { + warn!( + "Could not find an associated animation root for the tween event {:?}\n\ + help: make sure you have a `TimeRunner` defined on the entity you plan on animating", + data.event() + ); + return; + }; + + commands.trigger(data.event_on(root)); }); } From 7c27e393c8bd810956e239f6ee9109847394517a Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 10:48:56 +0000 Subject: [PATCH 32/34] fix: avoids a hard `Data: Debug` requirement for event system warnings --- src/tween_event.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index c422ccc..54976ee 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -256,16 +256,16 @@ pub fn tween_event_system( >, q_tween_roots: Query<(), With>, ) where - Data: Debug + Clone + Send + Sync + 'static, + Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { q_tween_event_data.iter().for_each(|(child_of, data)| { let root = child_of.parent(); if !q_tween_roots.contains(root) { warn!( - "Could not find an associated animation root for the tween event {:?}\n\ + "Could not find an associated animation root for a tween event on entity {:?}\n\ help: make sure you have a `TimeRunner` defined on the entity you plan on animating", - data.event() + data.event().entity, ); return; }; From 0626010cb3b998b0909852023eb0f3cf89d6d965 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Sun, 25 Oct 2026 11:09:01 +0000 Subject: [PATCH 33/34] tweak: adds info on the data type in missing root warn messages --- src/tween_event.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tween_event.rs b/src/tween_event.rs index 54976ee..a4770ef 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -263,9 +263,10 @@ pub fn tween_event_system( let root = child_of.parent(); if !q_tween_roots.contains(root) { warn!( - "Could not find an associated animation root for a tween event on entity {:?}\n\ + "Could not find an associated animation root for a tween event on entity {:?} with data {:?}\n\ help: make sure you have a `TimeRunner` defined on the entity you plan on animating", data.event().entity, + std::any::type_name::(), ); return; }; From 8aa34e414b988c1ce2ac8929788d973747264435 Mon Sep 17 00:00:00 2001 From: Alex Colby Date: Tue, 27 Oct 2026 15:51:21 +0000 Subject: [PATCH 34/34] feat: includes the original entity that created a tween event This lets people target the entity constructed by the `event`/`message` combinator using the `TweenEvent` that it emitted (for example to halt further events from that animation) --- src/tween_event.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tween_event.rs b/src/tween_event.rs index a4770ef..6ebd79b 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -177,6 +177,8 @@ pub struct TweenEvent { pub interpolation_value: Option, /// The entity that emitted the event pub entity: Entity, + /// The original tween entity that created the event + pub tween: Entity, } /// QueryData used to construct a [`TweenEvent`] for a given [`TweenEventData`] @@ -205,6 +207,7 @@ where progress: *self.progress, interpolation_value: self.interpolation_value.map(|v| v.0), entity: self.entity, + tween: self.entity, } }