Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add with_child to simplify spawning when there will only be one child #14594

Merged
merged 2 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions crates/bevy_hierarchy/src/child_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ pub trait BuildChildren {
/// Takes a closure which builds children for this entity using [`ChildBuild`].
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
/// already a child of this one, this method does nothing.
///
Expand Down Expand Up @@ -432,6 +435,13 @@ impl BuildChildren for EntityCommands<'_> {
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 {
let parent = self.id();
if children.contains(&parent) {
Expand Down Expand Up @@ -566,6 +576,17 @@ impl BuildChildren for EntityWorldMut<'_> {
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 {
let parent = self.id();
if child == parent {
Expand Down Expand Up @@ -692,6 +713,14 @@ mod tests {
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.
fn omit_events(world: &mut World, number: usize) {
let mut events_resource = world.resource_mut::<Events<HierarchyEvent>>();
Expand Down Expand Up @@ -859,6 +888,19 @@ mod tests {
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]
fn push_and_insert_and_remove_children_commands() {
let mut world = World::default();
Expand Down Expand Up @@ -1228,4 +1270,23 @@ mod tests {
let children = query.get(&world, parent);
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);
}
}
18 changes: 8 additions & 10 deletions examples/ui/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
background_color: NORMAL_BUTTON.into(),
..default()
})
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
"Button",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::srgb(0.9, 0.9, 0.9),
},
));
});
.with_child(TextBundle::from_section(
"Button",
TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 40.0,
color: Color::srgb(0.9, 0.9, 0.9),
},
));
});
}