Add with_child to simplify spawning when there will only be one child (#14594)
				
					
				
			# Objective
This idea came up in the context of a hypothetical "text sections as
entities" where text sections are children of a text bundle.
```rust
commands
    .spawn(TextBundle::default())
    .with_children(|parent} {
        parent.spawn(TextSection::from("Hello"));
    });
```
This is a bit cumbersome (but powerful and probably the way things are
headed). [`bsn!`](https://github.com/bevyengine/bevy/discussions/14437)
will eventually make this nicer, but in the mean time, this might
improve ergonomics for the common case where there is only one
`TextSection`.
## Solution
Add a `with_child` method to the `BuildChildren` trait that spawns a
single bundle and adds it as a child to the entity.
```rust
commands
    .spawn(TextBundle::default())
    .with_child(TextSection::from("Hello"));
```
## Testing
I added some tests, and modified the `button` example to use the new
method.
If any potential co-authors want to improve the tests, that would be
great.
## Alternatives
- Some sort of macro. See
https://github.com/tigregalis/bevy_spans_ent/blob/main/examples/macro.rs#L20.
I don't love this, personally, and it would probably be obsoleted by
`bsn!`.
- Wait for `bsn!`
- Add `with_children_batch` that takes an `Into<Iterator>` of bundles.
  ```rust
  with_children_batch(vec![TextSection::from("Hello")])
  ```
This is maybe not as useful as it sounds -- it only works with
homogeneous bundles, so no marker components or styles.
- If this doesn't seem valuable, doing nothing is cool with me.
			
			
This commit is contained in:
		
							parent
							
								
									4c2cef2223
								
							
						
					
					
						commit
						5b29402cc8
					
				@ -341,6 +341,9 @@ pub trait BuildChildren {
 | 
				
			|||||||
    /// Takes a closure which builds children for this entity using [`ChildBuild`].
 | 
					    /// Takes a closure which builds children for this entity using [`ChildBuild`].
 | 
				
			||||||
    fn with_children(&mut self, f: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self;
 | 
					    fn with_children(&mut self, f: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Spawns the passed bundle and adds it to this entity as a child.
 | 
				
			||||||
 | 
					    fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Pushes children to the back of the builder's children. For any entities that are
 | 
					    /// Pushes children to the back of the builder's children. For any entities that are
 | 
				
			||||||
    /// already a child of this one, this method does nothing.
 | 
					    /// already a child of this one, this method does nothing.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
@ -432,6 +435,13 @@ impl BuildChildren for EntityCommands<'_> {
 | 
				
			|||||||
        self
 | 
					        self
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
 | 
				
			||||||
 | 
					        let parent = self.id();
 | 
				
			||||||
 | 
					        let child = self.commands().spawn(bundle).id();
 | 
				
			||||||
 | 
					        self.commands().add(PushChild { parent, child });
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn push_children(&mut self, children: &[Entity]) -> &mut Self {
 | 
					    fn push_children(&mut self, children: &[Entity]) -> &mut Self {
 | 
				
			||||||
        let parent = self.id();
 | 
					        let parent = self.id();
 | 
				
			||||||
        if children.contains(&parent) {
 | 
					        if children.contains(&parent) {
 | 
				
			||||||
@ -566,6 +576,17 @@ impl BuildChildren for EntityWorldMut<'_> {
 | 
				
			|||||||
        self
 | 
					        self
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
 | 
				
			||||||
 | 
					        let child = self.world_scope(|world| world.spawn(bundle).id());
 | 
				
			||||||
 | 
					        if let Some(mut children_component) = self.get_mut::<Children>() {
 | 
				
			||||||
 | 
					            children_component.0.retain(|value| child != *value);
 | 
				
			||||||
 | 
					            children_component.0.push(child);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.insert(Children::from_entities(&[child]));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn add_child(&mut self, child: Entity) -> &mut Self {
 | 
					    fn add_child(&mut self, child: Entity) -> &mut Self {
 | 
				
			||||||
        let parent = self.id();
 | 
					        let parent = self.id();
 | 
				
			||||||
        if child == parent {
 | 
					        if child == parent {
 | 
				
			||||||
@ -692,6 +713,14 @@ mod tests {
 | 
				
			|||||||
        assert_eq!(world.get::<Children>(parent).map(|c| &**c), children);
 | 
					        assert_eq!(world.get::<Children>(parent).map(|c| &**c), children);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Assert the number of children in the parent's [`Children`] component if it exists.
 | 
				
			||||||
 | 
					    fn assert_num_children(world: &World, parent: Entity, num_children: usize) {
 | 
				
			||||||
 | 
					        assert_eq!(
 | 
				
			||||||
 | 
					            world.get::<Children>(parent).map(|c| c.len()).unwrap_or(0),
 | 
				
			||||||
 | 
					            num_children
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Used to omit a number of events that are not relevant to a particular test.
 | 
					    /// Used to omit a number of events that are not relevant to a particular test.
 | 
				
			||||||
    fn omit_events(world: &mut World, number: usize) {
 | 
					    fn omit_events(world: &mut World, number: usize) {
 | 
				
			||||||
        let mut events_resource = world.resource_mut::<Events<HierarchyEvent>>();
 | 
					        let mut events_resource = world.resource_mut::<Events<HierarchyEvent>>();
 | 
				
			||||||
@ -859,6 +888,19 @@ mod tests {
 | 
				
			|||||||
        assert_eq!(*world.get::<Parent>(children[1]).unwrap(), Parent(parent));
 | 
					        assert_eq!(*world.get::<Parent>(children[1]).unwrap(), Parent(parent));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn build_child() {
 | 
				
			||||||
 | 
					        let mut world = World::default();
 | 
				
			||||||
 | 
					        let mut queue = CommandQueue::default();
 | 
				
			||||||
 | 
					        let mut commands = Commands::new(&mut queue, &world);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let parent = commands.spawn(C(1)).id();
 | 
				
			||||||
 | 
					        commands.entity(parent).with_child(C(2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        queue.apply(&mut world);
 | 
				
			||||||
 | 
					        assert_eq!(world.get::<Children>(parent).unwrap().0.len(), 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn push_and_insert_and_remove_children_commands() {
 | 
					    fn push_and_insert_and_remove_children_commands() {
 | 
				
			||||||
        let mut world = World::default();
 | 
					        let mut world = World::default();
 | 
				
			||||||
@ -1228,4 +1270,23 @@ mod tests {
 | 
				
			|||||||
        let children = query.get(&world, parent);
 | 
					        let children = query.get(&world, parent);
 | 
				
			||||||
        assert!(children.is_err());
 | 
					        assert!(children.is_err());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn with_child() {
 | 
				
			||||||
 | 
					        let world = &mut World::new();
 | 
				
			||||||
 | 
					        world.insert_resource(Events::<HierarchyEvent>::default());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let a = world.spawn_empty().id();
 | 
				
			||||||
 | 
					        let b = ();
 | 
				
			||||||
 | 
					        let c = ();
 | 
				
			||||||
 | 
					        let d = ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        world.entity_mut(a).with_child(b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_num_children(world, a, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        world.entity_mut(a).with_child(c).with_child(d);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_num_children(world, a, 3);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -83,15 +83,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 | 
				
			|||||||
                    background_color: NORMAL_BUTTON.into(),
 | 
					                    background_color: NORMAL_BUTTON.into(),
 | 
				
			||||||
                    ..default()
 | 
					                    ..default()
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .with_children(|parent| {
 | 
					                .with_child(TextBundle::from_section(
 | 
				
			||||||
                    parent.spawn(TextBundle::from_section(
 | 
					                    "Button",
 | 
				
			||||||
                        "Button",
 | 
					                    TextStyle {
 | 
				
			||||||
                        TextStyle {
 | 
					                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
 | 
				
			||||||
                            font: asset_server.load("fonts/FiraSans-Bold.ttf"),
 | 
					                        font_size: 40.0,
 | 
				
			||||||
                            font_size: 40.0,
 | 
					                        color: Color::srgb(0.9, 0.9, 0.9),
 | 
				
			||||||
                            color: Color::srgb(0.9, 0.9, 0.9),
 | 
					                    },
 | 
				
			||||||
                        },
 | 
					                ));
 | 
				
			||||||
                    ));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user