Skip to content
Open
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
18 changes: 13 additions & 5 deletions crates/bevy_ecs/src/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,9 @@ pub fn validate_parent_has_component<C: Component>(
/// Returns a [`SpawnRelatedBundle`] that will insert the [`Children`] component, spawn a [`SpawnableList`] of entities with given bundles that
/// relate to the [`Children`] entity via the [`ChildOf`] component, and reserve space in the [`Children`] for each spawned entity.
///
/// Any additional arguments will be interpreted as bundles to be spawned.
/// Any additional arguments will be interpreted as bundles to be spawned. Note that even single
/// component bundles MUST be wrapped in parentheses. This is to prevent you from accidentally
/// spawning many separate single components instead of a single bundle.
///
/// Also see [`related`](crate::related) for a version of this that works with any [`RelationshipTarget`] type.
///
Expand All @@ -490,10 +492,10 @@ pub fn validate_parent_has_component<C: Component>(
/// world.spawn((
/// Name::new("Root"),
/// children![
/// Name::new("Child1"),
/// (Name::new("Child1")),
/// (
/// Name::new("Child2"),
/// children![Name::new("Grandchild")]
/// children![(Name::new("Grandchild"))]
/// )
/// ]
/// ));
Expand All @@ -504,11 +506,17 @@ pub fn validate_parent_has_component<C: Component>(
/// [`SpawnableList`]: crate::spawn::SpawnableList
#[macro_export]
macro_rules! children {
[$($child:expr),*$(,)?] => {
$crate::hierarchy::Children::spawn($crate::recursive_spawn!($($child),*))
[$(($($child:expr),*$(,)?)),*$(,)?] => {
$crate::hierarchy::Children::spawn($crate::recursive_spawn!($(($($child),*)),*))
};
}

//macro_rules! children {
// [$($child:expr),*$(,)?] => {
// $crate::hierarchy::Children::spawn($crate::recursive_spawn!($($child),*))
// };
//}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
30 changes: 15 additions & 15 deletions examples/3d/lighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,25 +220,25 @@ fn setup(
..default()
},
children![
TextSpan::new("Ambient light is on\n"),
TextSpan(format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops,)),
TextSpan(format!(
(TextSpan::new("Ambient light is on\n")),
(TextSpan(format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops,))),
(TextSpan(format!(
"Shutter speed: 1/{:.0}s\n",
1.0 / parameters.shutter_speed_s
)),
TextSpan(format!(
))),
(TextSpan(format!(
"Sensitivity: ISO {:.0}\n",
parameters.sensitivity_iso
)),
TextSpan::new("\n\n"),
TextSpan::new("Controls\n"),
TextSpan::new("---------------\n"),
TextSpan::new("Arrow keys - Move objects\n"),
TextSpan::new("Space - Toggle ambient light\n"),
TextSpan::new("1/2 - Decrease/Increase aperture\n"),
TextSpan::new("3/4 - Decrease/Increase shutter speed\n"),
TextSpan::new("5/6 - Decrease/Increase sensitivity\n"),
TextSpan::new("R - Reset exposure"),
))),
(TextSpan::new("\n\n")),
(TextSpan::new("Controls\n")),
(TextSpan::new("---------------\n")),
(TextSpan::new("Arrow keys - Move objects\n")),
(TextSpan::new("Space - Toggle ambient light\n")),
(TextSpan::new("1/2 - Decrease/Increase aperture\n")),
(TextSpan::new("3/4 - Decrease/Increase shutter speed\n")),
(TextSpan::new("5/6 - Decrease/Increase sensitivity\n")),
(TextSpan::new("R - Reset exposure")),
],
));

Expand Down
2 changes: 1 addition & 1 deletion examples/app/log_layers_ecs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fn print_logs(
TextSpan::new(format!("{:5} ", event.level)),
TextColor(level_color(&event.level)),
),
TextSpan::new(&event.message),
(TextSpan::new(&event.message)),
],
));
}
Expand Down
2 changes: 1 addition & 1 deletion examples/diagnostics/log_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ fn update_commands(
..default()
},
children![
Text::new("[Q] Toggle filtering:"),
(Text::new("[Q] Toggle filtering:")),
(
Text::new(format!("{:?}", *status)),
TextColor(enabled_color(enabled))
Expand Down
12 changes: 6 additions & 6 deletions examples/input/text_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
..default()
},
children![
TextSpan::new("Click to toggle IME. Press return to start a new line.\n\n",),
TextSpan::new("IME Enabled: "),
TextSpan::new("false\n"),
TextSpan::new("IME Active: "),
TextSpan::new("false\n"),
TextSpan::new("IME Buffer: "),
(TextSpan::new("Click to toggle IME. Press return to start a new line.\n\n",)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This choice introduces significant syntactic noise. I personally still think the "parentheses are optional" rule is preferable.

The the intuitive read of this list is correct:

children! [
  A,
  B,
  C,
]

Rust trains us to read this as "this is a list of the children A, B, and C".

The grammar is "this is a list of children: if you want a child to have more than one component, add parenthesis". That is not so hard to learn that it justifies the added labor of changing it to "this is a list of children, each child must have parenthesis".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wherever this lands, the resulting choice needs to be in sync with BSN

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mm, I really understand where you're coming from, and if I didn't experience this confusion myself a couple times, I would probably agree with you.

I think the confusion comes from the fact that conceptually bundles are also lists. Given that lists of lists of components are not a common pattern anywhere else, it's super easy to miss the extra nesting. This in itself would again not be so bad if the resulting bugs are usually not immediately obvious and quite difficult to diagnose.

Not to sound too melodramatic, but I fear that a novice making this mistake by accident could be inclined to write Bevy off as unrealible. It really doesnt take long for a new user to run into the children macro, especially when doing ui related stuff.

Finally, I was also never a huge fan of the parenthesis solution, would you maybe have something else in mind?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main here for me is how often are you creating children with a single component like children![A, B, C]? As a user of bevy I don't think I've ever done this, usually its a group of components I want to add.

Copy link
Contributor Author

@HugoPeters1024 HugoPeters1024 Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite common when building ui's to have multiple children with just a Node or just a Text.

Conversely, since required components I often have just a Sprite initially. Then later when you want to add some logic and slap a marker component on it, you have to be very carefully to remember the parenthesis.

(TextSpan::new("IME Enabled: ")),
(TextSpan::new("false\n")),
(TextSpan::new("IME Active: ")),
(TextSpan::new("false\n")),
(TextSpan::new("IME Buffer: ")),
(
TextSpan::new("\n"),
TextFont {
Expand Down
14 changes: 7 additions & 7 deletions examples/math/render_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,15 +354,15 @@ fn setup_text(mut commands: Commands, cameras: Query<(Entity, &Camera)>) {
HeaderText,
TextLayout::new_with_justify(Justify::Center),
children![
TextSpan::new("Primitive: "),
TextSpan(format!("{text}", text = PrimitiveSelected::default())),
TextSpan::new("\n\n"),
TextSpan::new(
(TextSpan::new("Primitive: ")),
(TextSpan(format!("{text}", text = PrimitiveSelected::default()))),
(TextSpan::new("\n\n")),
(TextSpan::new(
"Press 'C' to switch between 2D and 3D mode\n\
Press 'Up' or 'Down' to switch to the next/previous primitive",
),
TextSpan::new("\n\n"),
TextSpan::new("(If nothing is displayed, there's no rendering support yet)",),
)),
(TextSpan::new("\n\n")),
(TextSpan::new("(If nothing is displayed, there's no rendering support yet)",)),
]
)],
));
Expand Down
10 changes: 5 additions & 5 deletions examples/shader/shader_prepass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ fn setup(
..default()
},
children![
TextSpan::new("Prepass Output: transparent\n"),
TextSpan::new("\n\n"),
TextSpan::new("Controls\n"),
TextSpan::new("---------------\n"),
TextSpan::new("Space - Change output\n"),
(TextSpan::new("Prepass Output: transparent\n")),
(TextSpan::new("\n\n")),
(TextSpan::new("Controls\n")),
(TextSpan::new("---------------\n")),
(TextSpan::new("Space - Change output\n")),
],
));
}
Expand Down
6 changes: 3 additions & 3 deletions examples/testbed/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,14 +665,14 @@ mod linear_gradient {
angle: LinearGradient::TO_RIGHT,
stops: stops.clone(),
}),
children![
children![(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this was probably intended to be spawned as a single bundle, as having separate entities with just a Text and a TextFont respectively seems nonsensical.

If so, then I believe this highlights the value of this PR ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the CI confirms this https://pixel-eagle.com/project/b04f67c0-c054-4a6f-92ec-f599fec2fd1d/run/22845/compare/22830?screenshot=testbed_ui/screenshot-LinearGradient.png (what an amazing setup BTW!). I feel like we probably want to use different font settings as this makes the output less readable.

Node {
position_type: PositionType::Absolute,
..default()
},
TextFont::from_font_size(10.),
bevy::ui::widget::Text(format!("{color_space:?}")),
]
bevy::ui::widget::Text(format!("{color_space:?}"))
),]
)],
));
}
Expand Down
36 changes: 18 additions & 18 deletions examples/tools/gamepad_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,34 +138,34 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
Transform::from_xyz(BUTTONS_X, BUTTONS_Y, 0.),
Visibility::default(),
children![
GamepadButtonBundle::new(
(GamepadButtonBundle::new(
GamepadButton::North,
meshes.circle.clone(),
materials.normal.clone(),
0.,
BUTTON_CLUSTER_RADIUS,
),
GamepadButtonBundle::new(
)),
(GamepadButtonBundle::new(
GamepadButton::South,
meshes.circle.clone(),
materials.normal.clone(),
0.,
-BUTTON_CLUSTER_RADIUS,
),
GamepadButtonBundle::new(
)),
(GamepadButtonBundle::new(
GamepadButton::West,
meshes.circle.clone(),
materials.normal.clone(),
-BUTTON_CLUSTER_RADIUS,
0.,
),
GamepadButtonBundle::new(
)),
(GamepadButtonBundle::new(
GamepadButton::East,
meshes.circle.clone(),
materials.normal.clone(),
BUTTON_CLUSTER_RADIUS,
0.,
),
)),
],
));

Expand Down Expand Up @@ -193,37 +193,37 @@ fn setup(mut commands: Commands, meshes: Res<ButtonMeshes>, materials: Res<Butto
Transform::from_xyz(-BUTTONS_X, BUTTONS_Y, 0.),
Visibility::default(),
children![
GamepadButtonBundle::new(
(GamepadButtonBundle::new(
GamepadButton::DPadUp,
meshes.triangle.clone(),
materials.normal.clone(),
0.,
BUTTON_CLUSTER_RADIUS,
),
GamepadButtonBundle::new(
)),
(GamepadButtonBundle::new(
GamepadButton::DPadDown,
meshes.triangle.clone(),
materials.normal.clone(),
0.,
-BUTTON_CLUSTER_RADIUS,
)
.with_rotation(PI),
GamepadButtonBundle::new(
.with_rotation(PI)),
(GamepadButtonBundle::new(
GamepadButton::DPadLeft,
meshes.triangle.clone(),
materials.normal.clone(),
-BUTTON_CLUSTER_RADIUS,
0.,
)
.with_rotation(PI / 2.),
GamepadButtonBundle::new(
.with_rotation(PI / 2.)),
(GamepadButtonBundle::new(
GamepadButton::DPadRight,
meshes.triangle.clone(),
materials.normal.clone(),
BUTTON_CLUSTER_RADIUS,
0.,
)
.with_rotation(-PI / 2.),
.with_rotation(-PI / 2.)),
],
));

Expand Down Expand Up @@ -277,7 +277,7 @@ fn setup_sticks(
Transform::from_xyz(x_pos, y_pos, 0.),
Visibility::default(),
children![
Sprite::from_color(DEAD_COLOR, Vec2::splat(STICK_BOUNDS_SIZE * 2.),),
(Sprite::from_color(DEAD_COLOR, Vec2::splat(STICK_BOUNDS_SIZE * 2.),)),
(
Sprite::from_color(LIVE_COLOR, Vec2::splat(live_size)),
Transform::from_xyz(live_mid, live_mid, 2.),
Expand Down Expand Up @@ -369,7 +369,7 @@ fn setup_connected(mut commands: Commands) {
..default()
},
ConnectedGamepadsText,
children![TextSpan::new("None")],
children![(TextSpan::new("None"))],
));
}

Expand Down
8 changes: 4 additions & 4 deletions examples/ui/borders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,10 @@ fn setup(mut commands: Commands) {
},
BackgroundColor(Color::srgb(0.25, 0.25, 0.25)),
children![
label("Borders"),
borders_examples,
label("Borders Rounded"),
borders_examples_rounded
(label("Borders")),
(borders_examples),
(label("Borders Rounded")),
(borders_examples_rounded)
],
));
}
Expand Down
Loading
Loading