recompose_core/
modify.rs

1use crate::{
2    dyn_compose::DynCompose,
3    keyed::Keyed,
4    state::{GetStateId, SetState, TypedStateId},
5    Compose,
6};
7use bevy_ecs::{
8    bundle::Bundle,
9    entity::Entity,
10    event::Event,
11    observer::{Observer, Trigger},
12    system::{EntityCommands, IntoObserverSystem},
13};
14use bevy_picking::events::{Out, Over, Pointer};
15use std::{hash::Hash, sync::Arc};
16
17// Storing observers directly would be better, but it's a little tricky, so for now we store a function that adds
18// the observer given entity commands.
19type ObserverGeneratorFn = Arc<dyn (Fn(&mut EntityCommands) -> Entity) + Send + Sync>;
20
21#[derive(Clone)]
22pub(crate) struct ObserverGenerator(ObserverGeneratorFn);
23
24impl ObserverGenerator {
25    fn new<E: Event, B: Bundle, M>(
26        observer: impl IntoObserverSystem<E, B, M> + Clone + Sync,
27    ) -> Self {
28        let f = Arc::new(move |entity: &mut EntityCommands| {
29            let target_entity = entity.id();
30            let commands = entity.commands_mut();
31            let o = Observer::new(observer.clone()).with_entity(target_entity);
32            commands.spawn(o).id()
33        });
34
35        Self(f)
36    }
37
38    pub fn generate(&self, entity: &mut EntityCommands) -> Entity {
39        self.0(entity)
40    }
41}
42
43/// Modifiers hold information about children and observers. Used together with the [`Modify`](Modify) trait they enable
44/// a compoosable to add ECS children and observers to the spawned entity.
45///
46/// We can forward the modifier to the underlying [`Spawn`](crate::spawn::Spawn) by using the [`use_modifier`](Modify::use_modifier)
47/// function.
48///
49/// # Example
50/// ```
51/// fn compose(cx: &mut Scope) -> impl Compose {
52///    Button {
53///       label: "Click me".to_string(),
54///       modifier: Modifier::default()
55///    }
56///    .observe(move |_: Trigger<Pointer<Click>>| {
57///        println!("Button clicked!");
58///    });
59///
60/// }
61///
62/// #[derive(Clone)]
63/// struct Button {
64///     label: String
65///     modifier: Modifier,
66/// }
67///
68/// impl Modify for Button {
69///    fn modifier(&mut self) -> &mut Modifier {
70///       &mut self.modifier
71///   }
72/// }
73///
74/// impl Compose for Button {
75///    fn compose<'a>(&self, cx: &mut Scope) -> impl Compose + 'a {
76///       Text::new(self.label.clone())
77///           .use_modifier(&self.modifier)
78///    }
79/// }
80/// ```
81#[derive(Clone, Default)]
82#[allow(clippy::type_complexity)]
83pub struct Modifier {
84    pub(crate) children: DynCompose,
85    pub(crate) bundle_modifiers: Vec<Arc<dyn Fn(&mut EntityCommands) + Send + Sync>>,
86    pub(crate) temporary_observers: Vec<ObserverGenerator>,
87    pub(crate) retained_observers: Vec<ObserverGenerator>,
88}
89
90impl Modifier {
91    /// Joins two modifiers together. Note, the the newest children will override the old children.
92    pub fn join(&mut self, other: &Modifier) {
93        self.children = match other.children.is_empty() {
94            true => self.children.clone(),
95            false => other.children.clone(),
96        };
97        self.bundle_modifiers
98            .extend(other.bundle_modifiers.iter().cloned());
99        self.temporary_observers
100            .extend(other.temporary_observers.iter().cloned());
101        self.retained_observers
102            .extend(other.retained_observers.iter().cloned());
103    }
104}
105
106/// The `Modify` trait is used to modify entities before they are spawned or recomposed. It is used to add children and
107/// observers to the entity. See the [`Modifier`](Modifier) struct for more information.
108pub trait Modify: Sized {
109    fn modifier(&mut self) -> &mut Modifier;
110}
111
112impl<T: Modify + Compose> ModifyFunctions<T> for T {
113    type Target = T;
114
115    fn use_modifier(mut self, modifier: &Modifier) -> Self {
116        self.modifier().join(modifier);
117        self
118    }
119
120    fn children(mut self, children: impl Compose + 'static) -> Self {
121        let modifier = self.modifier();
122        modifier.children = DynCompose::new(children);
123        self
124    }
125
126    fn with_bundle<B: Bundle + Clone>(mut self, bundle: B) -> Self::Target {
127        let bundle_modifier = Arc::new(move |entity: &mut EntityCommands| {
128            entity.try_insert(bundle.clone());
129        });
130
131        let modifier = self.modifier();
132        modifier.bundle_modifiers.push(bundle_modifier);
133
134        self
135    }
136
137    fn with_bundle_if<B: Bundle + Clone>(mut self, condition: bool, bundle: B) -> Self::Target {
138        let bundle_modifier = Arc::new(move |entity: &mut EntityCommands| {
139            if condition {
140                entity.try_insert(bundle.clone());
141            } else {
142                entity.remove::<B>();
143            }
144        });
145
146        let modifier = self.modifier();
147        modifier.bundle_modifiers.push(bundle_modifier);
148
149        self
150    }
151
152    fn to_dyn(self) -> DynCompose
153    where
154        Self: 'static,
155    {
156        DynCompose::new(self)
157    }
158
159    fn some(self) -> Option<Self::Target> {
160        Some(self)
161    }
162
163    fn keyed<H: Hash + Send + Sync>(self, key: H) -> Keyed<H>
164    where
165        Self: 'static,
166    {
167        Keyed::new(key, self)
168    }
169
170    fn observe<E: Event, B2: Bundle, M>(
171        mut self,
172        observer: impl IntoObserverSystem<E, B2, M> + Clone + Sync,
173    ) -> Self {
174        let observer_generator = ObserverGenerator::new(observer);
175        let modifier = self.modifier();
176        modifier.temporary_observers.push(observer_generator);
177
178        self
179    }
180
181    fn observe_retained<E: Event, B2: Bundle, M>(
182        mut self,
183        observer: impl IntoObserverSystem<E, B2, M> + Clone + Sync,
184    ) -> Self {
185        let observer_generator = ObserverGenerator::new(observer);
186        let modifier = self.modifier();
187        modifier.retained_observers.push(observer_generator);
188
189        self
190    }
191
192    fn bind_hover(self, hover_state: impl GetStateId<bool>) -> Self {
193        let typed_state_id = TypedStateId::from_state_id(hover_state.get_id());
194
195        self.observe_retained(move |_: Trigger<Pointer<Over>>, mut state: SetState| {
196            state.set_neq(typed_state_id, true)
197        })
198        .observe_retained(move |_: Trigger<Pointer<Out>>, mut state: SetState| {
199            state.set_neq(typed_state_id, false)
200        })
201    }
202}
203
204/// The `ModifyFunctions` trait provides a template for the functions of the [`Modify`](Modify) trait. The reason why
205/// it is split from the actual `Modify` trait is that [`BundleExtension`](crate::bundle_extension::BundleExtension)
206/// also implements it.
207///
208// The generic on the ModfiyFunctions trait doesn't do anything, and is here just not to have conflicting trait
209// implementations for `BundleExtension` and `Modify`. Hacky but it works.
210pub trait ModifyFunctions<T>: Sized {
211    type Target: Compose;
212    // Uses given modifier
213    fn use_modifier(self, modifier: &Modifier) -> Self::Target;
214
215    /// Sets the children of the spawned entity.
216    fn children(self, children: impl Compose + 'static) -> Self::Target;
217
218    // TODO: When `ObservedBy` is exposed, we should just retain it and SpawnComposable between each rerender and remove
219    // all other components, so that we don't need to worry about removing conditional bundle components ourselves. This
220    // will make this logic a lot simpler.
221
222    /// Add a bundle to the spawned entity. This is useful when you want to extend the spawned bundle with additional
223    /// components.
224    ///
225    /// For the [`Spawn`](crate::spawn::Spawn)-composable, the conditional bundles are always added before the main
226    /// bundle, which means that the "main" bundle (of the same type) will always override the conditional bundles.
227    fn with_bundle<B: Bundle + Clone>(self, bundle: B) -> Self::Target;
228
229    /// Add a bundle to the spawned entity if the condition is true. When the condition is false, we're actively trying
230    /// to remove the bundle each time the compsable recomposes.
231    ///
232    /// For the [`Spawn`](crate::spawn::Spawn)-composable, the conditional bundles are always added before the main
233    /// bundle, which means that the "main" bundle (of the same type) will always override the conditional bundles.
234    fn with_bundle_if<B: Bundle + Clone>(self, condition: bool, bundle: B) -> Self::Target;
235
236    /// Converts this `Compose` into `DynCompose`.
237    fn to_dyn(self) -> DynCompose
238    where
239        Self::Target: 'static;
240
241    /// Wraps this `Compose` in `Some`.
242    fn some(self) -> Option<Self::Target>;
243
244    /// Wraps this `Compose` in `Some` if the condition is met, otherwise returns `None`.
245    fn some_if(self, condition: bool) -> Option<Self::Target> {
246        match condition {
247            true => self.some(),
248            false => None,
249        }
250    }
251
252    /// Wraps this `Compose` in a `Keyed` compose.
253    fn keyed<H: Hash + Send + Sync>(self, key: H) -> Keyed<H>
254    where
255        Self::Target: 'static;
256
257    /// Adds an observer to the spawned entity. Observers are created and removed each time the composable recomposes.
258    /// If you want to retain the observer, use the [`observe_retained`](Modify::observe_retained) function.
259    fn observe<E: Event, B2: Bundle, M>(
260        self,
261        observer: impl IntoObserverSystem<E, B2, M> + Clone + Sync,
262    ) -> Self::Target;
263
264    /// Adds an observer to the spawned entity. Retained observers are only added once, when the entity is first spawned.
265    fn observe_retained<E: Event, B2: Bundle, M>(
266        self,
267        observer: impl IntoObserverSystem<E, B2, M> + Clone + Sync,
268    ) -> Self::Target;
269
270    /// Binds the given state to the hovered state of the entity.
271    fn bind_hover(self, hover_state: impl GetStateId<bool>) -> Self::Target;
272}