diff --git a/examples/keybinding_menu.rs b/examples/keybinding_menu.rs
index 1fefd18e..649f0d15 100644
--- a/examples/keybinding_menu.rs
+++ b/examples/keybinding_menu.rs
@@ -2,6 +2,10 @@ use core::{error::Error, fmt::Write};
use std::fs;
use bevy::{
+ ecs::{
+ relationship::RelatedSpawner,
+ spawn::{SpawnWith, SpawnableList},
+ },
input::{ButtonState, common_conditions::*, keyboard::KeyboardInput, mouse::MouseButtonInput},
prelude::*,
ui::FocusPolicy,
@@ -59,41 +63,41 @@ fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
// We use separate root node to let dialogs cover the whole UI.
- commands
- .spawn(Node {
+ commands.spawn((
+ Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
..Default::default()
- })
- .with_children(|parent| {
- parent
- .spawn(Node {
- flex_direction: FlexDirection::Column,
- width: Val::Percent(100.0),
- height: Val::Percent(100.0),
- padding: PADDING,
- row_gap: GAP,
- ..Default::default()
- })
- .with_children(|parent| {
- setup_actions(parent, &settings);
-
- parent
- .spawn(Node {
- align_items: AlignItems::End,
- width: Val::Percent(100.0),
- height: Val::Percent(100.0),
- justify_content: JustifyContent::End,
- ..Default::default()
- })
- .with_children(|parent| {
- parent
- .spawn(SettingsButton)
- .with_child(Text::new("Apply"))
- .observe(apply);
- });
- });
- });
+ },
+ children![(
+ Node {
+ flex_direction: FlexDirection::Column,
+ width: Val::Percent(100.0),
+ height: Val::Percent(100.0),
+ padding: PADDING,
+ row_gap: GAP,
+ ..Default::default()
+ },
+ children![
+ actions_grid(settings.clone()),
+ (
+ Node {
+ align_items: AlignItems::End,
+ width: Val::Percent(100.0),
+ height: Val::Percent(100.0),
+ justify_content: JustifyContent::End,
+ ..Default::default()
+ },
+ children![(
+ SettingsButton,
+ Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner<_>| {
+ parent.spawn(Text::new("Apply")).observe(apply);
+ }))
+ )],
+ )
+ ]
+ )],
+ ));
commands.insert_resource(settings);
}
@@ -122,85 +126,70 @@ struct SettingsField(&'static str);
/// Number of input columns.
const INPUTS_PER_ACTION: usize = 3;
-fn setup_actions(parent: &mut ChildSpawnerCommands, settings: &KeyboardSettings) -> Entity {
- parent
- .spawn(Node {
+fn actions_grid(settings: KeyboardSettings) -> impl Bundle {
+ (
+ Node {
display: Display::Grid,
column_gap: GAP,
row_gap: GAP,
grid_template_columns: vec![GridTrack::auto(); INPUTS_PER_ACTION + 1],
..Default::default()
- })
- .with_children(|parent| {
- // We could utilzie reflection to iterate over fields,
- // but in real application you most likely want to have a nice and translatable text on buttons.
- setup_action_row(
- parent,
+ },
+ // We could utilzie reflection to iterate over fields,
+ // but in real application you most likely want to have a nice and translatable text on buttons.
+ Children::spawn((
+ action_row(
"Forward",
- &settings.forward,
settings_field!(settings.forward),
- );
- setup_action_row(
- parent,
- "Left",
- &settings.left,
- settings_field!(settings.left),
- );
- setup_action_row(
- parent,
+ settings.forward,
+ ),
+ action_row("Left", settings_field!(settings.left), settings.left),
+ action_row(
"Backward",
- &settings.backward,
settings_field!(settings.backward),
- );
- setup_action_row(
- parent,
- "Right",
- &settings.right,
- settings_field!(settings.right),
- );
- setup_action_row(
- parent,
- "Jump",
- &settings.jump,
- settings_field!(settings.jump),
- );
- setup_action_row(parent, "Run", &settings.run, settings_field!(settings.run));
- })
- .id()
+ settings.backward,
+ ),
+ action_row("Right", settings_field!(settings.right), settings.right),
+ action_row("Jump", settings_field!(settings.jump), settings.jump),
+ action_row("Run", settings_field!(settings.run), settings.run),
+ )),
+ )
}
-fn setup_action_row(
- parent: &mut ChildSpawnerCommands,
+fn action_row(
name: &'static str,
- inputs: &[Input],
field: SettingsField,
-) {
- parent.spawn((Text::new(name), DARK_TEXT));
- for index in 0..INPUTS_PER_ACTION {
- parent
- .spawn(Node {
- column_gap: GAP,
- align_items: AlignItems::Center,
- ..Default::default()
- })
- .with_children(|parent| {
- let button_entity = parent
- .spawn((
- field,
- Name::new(name),
- InputButton {
- input: inputs.get(index).copied(),
- },
- ))
- .with_child(Text::default()) // Will be updated automatically on `InputButton` insertion
- .observe(show_binding_dialog)
- .id();
- parent
- .spawn(DeleteButton { button_entity })
- .with_child(Text::new("X"))
- .observe(delete_binding);
- });
- }
+ inputs: Vec,
+) -> impl SpawnableList {
+ (
+ Spawn((Text::new(name), DARK_TEXT)),
+ SpawnWith(move |parent: &mut RelatedSpawner<_>| {
+ for index in 0..INPUTS_PER_ACTION {
+ let input = inputs.get(index).copied();
+ parent.spawn((
+ Node {
+ column_gap: GAP,
+ align_items: AlignItems::Center,
+ ..Default::default()
+ },
+ Children::spawn(SpawnWith(move |parent: &mut RelatedSpawner<_>| {
+ let button_entity = parent
+ .spawn((
+ field,
+ Name::new(name),
+ InputButton { input },
+ children![Text::default()], // Will be updated automatically on `InputButton` insertion
+ ))
+ .observe(show_binding_dialog)
+ .id();
+ parent
+ .spawn((DeleteButton { button_entity }, children![Text::new("X")]))
+ .observe(delete_binding);
+ })),
+ ));
+ }
+ }),
+ )
}
fn delete_binding(
@@ -225,36 +214,30 @@ fn show_binding_dialog(
let name = names.get(trigger.target()).unwrap();
info!("starting binding for '{name}'");
- commands.entity(*root_entity).with_children(|parent| {
- parent
- .spawn(BindingDialog {
- button_entity: trigger.target(),
- })
- .with_children(|parent| {
- parent
- .spawn((
- Node {
- flex_direction: FlexDirection::Column,
- padding: PADDING,
- row_gap: GAP,
- ..Default::default()
- },
- PANEL_BACKGROUND,
- ))
- .with_children(|parent| {
- parent.spawn((
- TextLayout {
- justify: JustifyText::Center,
- ..Default::default()
- },
- DARK_TEXT,
- Text::new(format!(
- "Binding \"{name}\", \npress any key or Esc to cancel",
- )),
- ));
- });
- });
- });
+ commands.entity(*root_entity).with_child((
+ BindingDialog {
+ button_entity: trigger.target(),
+ },
+ children![(
+ Node {
+ flex_direction: FlexDirection::Column,
+ padding: PADDING,
+ row_gap: GAP,
+ ..Default::default()
+ },
+ PANEL_BACKGROUND,
+ children![(
+ TextLayout {
+ justify: JustifyText::Center,
+ ..Default::default()
+ },
+ DARK_TEXT,
+ Text::new(format!(
+ "Binding \"{name}\", \npress any key or Esc to cancel",
+ )),
+ )]
+ )],
+ ));
}
fn bind(
@@ -286,47 +269,42 @@ fn bind(
{
info!("found conflict with '{name}' for '{input}'");
- commands.entity(*root_entity).with_children(|parent| {
- parent
- .spawn(ConflictDialog {
- button_entity: dialog.button_entity,
- conflict_entity,
- })
- .with_children(|parent| {
- parent
- .spawn((
- Node {
- flex_direction: FlexDirection::Column,
- align_items: AlignItems::Center,
- padding: PADDING,
- row_gap: GAP,
- ..Default::default()
- },
- PANEL_BACKGROUND,
- ))
- .with_children(|parent| {
- parent.spawn((
- DARK_TEXT,
- Text::new(format!("\"{input}\" is already used by \"{name}\"",)),
- ));
+ commands.entity(*root_entity).with_child((
+ ConflictDialog {
+ button_entity: dialog.button_entity,
+ conflict_entity,
+ },
+ children![(
+ Node {
+ flex_direction: FlexDirection::Column,
+ align_items: AlignItems::Center,
+ padding: PADDING,
+ row_gap: GAP,
+ ..Default::default()
+ },
+ PANEL_BACKGROUND,
+ children![
+ (
+ DARK_TEXT,
+ Text::new(format!("\"{input}\" is already used by \"{name}\"",)),
+ ),
+ (
+ Node {
+ column_gap: GAP,
+ ..Default::default()
+ },
+ Children::spawn(SpawnWith(|parent: &mut RelatedSpawner<_>| {
parent
- .spawn(Node {
- column_gap: GAP,
- ..Default::default()
- })
- .with_children(|parent| {
- parent
- .spawn(SettingsButton)
- .with_child(Text::new("Replace"))
- .observe(replace_binding);
- parent
- .spawn(SettingsButton)
- .with_child(Text::new("Cancel"))
- .observe(cancel_replace_binding);
- });
- });
- });
- });
+ .spawn((SettingsButton, children![Text::new("Replace")]))
+ .observe(replace_binding);
+ parent
+ .spawn((SettingsButton, children![Text::new("Cancel")]))
+ .observe(cancel_replace_binding);
+ }))
+ )
+ ]
+ )],
+ ));
} else {
let (_, name, mut button) = buttons
.get_mut(dialog.button_entity)