bevyengine/bevy

Despawned Node Entity Continues to Affect Layout

ncallaway opened this issue · 3 comments

An UI Node that is despawned continues to impact the layout of it's parent Node as if it were still present. I think it's likely that the despawn does not cause the underlying Stretch node to be removed.

Example Reproduction

use bevy::prelude::*;

/// This example illustrates how to create a button that changes color and text based on its interaction state.
fn main() {
    App::build()
        .add_default_plugins()
        .init_resource::<ButtonMaterials>()
        .init_resource::<ButtonCount>()
        .add_startup_system(setup.system())
        .add_system(button_system.system())
        .run();
}

fn button_system(
    mut commands: Commands,
    button_materials: Res<ButtonMaterials>,
    asset_server: Res<AssetServer>,
    mut button_count: ResMut<ButtonCount>,
    mut interaction_query: Query<(
        Entity,
        &Button,
        Mutated<Interaction>,
        &mut Handle<ColorMaterial>,
        &Children,
    )>,
    mut root_query: Query<(Entity, &Root, &Children)>,
) {
    for (e, _, interaction, mut material, _) in &mut interaction_query.iter() {
        match *interaction {
            Interaction::Clicked => {
                let font = asset_server
                    .get_handle("assets/fonts/FiraMono-Medium.ttf")
                    .unwrap();

                // we're going to remvoe the button
                commands.despawn_recursive(e);

                // spawn a new button in the same spot
                for (parent, _, children) in &mut root_query.iter() {
                    println!("Now have: {} children", children.0.len());
                    let e = spawn_button_node(
                        &mut commands,
                        &button_materials,
                        font,
                        &(format!("Button {}", button_count.0))[..],
                    );
                    button_count.0 = button_count.0 + 1;
                    commands.push_children(parent, &[e]);
                    break;
                }
            }
            Interaction::Hovered => {
                *material = button_materials.hovered;
            }
            Interaction::None => {
                *material = button_materials.normal;
            }
        }
    }
}

fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    button_materials: Res<ButtonMaterials>,
) {
    let font = asset_server
        .load("assets/fonts/FiraMono-Medium.ttf")
        .unwrap();

    let root = Entity::new();
    commands
        .spawn(UiCameraComponents::default())
        .spawn_as_entity(
            root,
            NodeComponents {
                style: Style {
                    size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
                    flex_direction: FlexDirection::ColumnReverse,
                    justify_content: JustifyContent::FlexStart,
                    align_items: AlignItems::Center,
                    ..Default::default()
                },
                ..Default::default()
            },
        )
        .with(Root);
    let button = spawn_button_node(&mut commands, &button_materials, font, "First button");
    commands.push_children(root, &[button]);
}

struct ButtonMaterials {
    normal: Handle<ColorMaterial>,
    hovered: Handle<ColorMaterial>,
    pressed: Handle<ColorMaterial>,
}

impl FromResources for ButtonMaterials {
    fn from_resources(resources: &Resources) -> Self {
        let mut materials = resources.get_mut::<Assets<ColorMaterial>>().unwrap();
        ButtonMaterials {
            normal: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
            hovered: materials.add(Color::rgb(0.05, 0.05, 0.05).into()),
            pressed: materials.add(Color::rgb(0.1, 0.5, 0.1).into()),
        }
    }
}

#[derive(Default)]
struct ButtonCount(u32);

struct Root;

fn spawn_button_node(
    commands: &mut Commands,
    mats: &Res<ButtonMaterials>,
    font: Handle<Font>,
    label: &str,
) -> Entity {
    let e = Entity::new();
    commands
        .spawn_as_entity(
            e,
            ButtonComponents {
                style: Style {
                    size: Size::new(Val::Px(300.0), Val::Px(65.0)),
                    // center button
                    margin: Rect::all(Val::Auto),
                    // horizontally center child text
                    justify_content: JustifyContent::Center,
                    // vertically center child text
                    align_items: AlignItems::Center,
                    ..Default::default()
                },
                material: mats.normal,
                ..Default::default()
            },
        )
        .with_children(|parent| {
            parent.spawn(TextComponents {
                text: Text {
                    value: label.to_string(),
                    font: font,
                    style: TextStyle {
                        font_size: 28.0,
                        color: Color::rgb(0.8, 0.8, 0.8),
                    },
                },
                ..Default::default()
            });
        });

    e
}

In this example everytime the button is clicked we despawn the existing button, and then spawn a new button inside the same container. My expectation is that since the container only ever holds one child, that child remains in the same position.

Demo

despawn-layout

For comparison, this is the behaviour without despawning the button

no-despawn-layout

Suggested labels: bug | ui

In the new version of bevy, I’ve replicated your code, and it’s working exactly as you’d expect.
I simplified a few places and changed the font file because I don’t have that font asset of yours. I think this bug has been fixed.

use bevy::prelude::*;

/// This example illustrates how to create a button that changes color and text based on its interaction state.
fn main() {
    App::build()
        .add_default_plugins()
        .init_resource::<ButtonMaterials>()
        .init_resource::<ButtonCount>()
        .add_startup_system(setup.system())
        .add_system(button_system.system())
        .run();
}

fn button_system(
    mut commands: Commands,
    button_materials: Res<ButtonMaterials>,
    mut button_count: ResMut<ButtonCount>,
    asset_server: Res<AssetServer>,
    mut interaction_query: Query<(
        Entity,
        &Button,
        Mutated<Interaction>,
        &mut Handle<ColorMaterial>,
        &Children,
    )>,
    mut root_query: Query<(Entity, &Root, &Children)>,
) {
    for (_, _, interaction, mut material, _) in &mut interaction_query.iter() {
        match *interaction {
            Interaction::Clicked => {
                // we're going to remvoe the button
                // commands.despawn_recursive(e);
                let font = asset_server.get_handle("assets/fonts/FiraCode-Light.ttf").unwrap_or_default();
                // spawn a new button in the same spot
                for (parent, _, children) in &mut root_query.iter() {
                    println!("Now have: {} children", children.0.len());
                    let e = spawn_button_node(
                        &mut commands,
                        font,
                        &button_materials,
                        &(format!("Button {}", button_count.0))[..],
                    );
                    button_count.0 = button_count.0 + 1;
                    commands.push_children(parent, &[e]);
                    break;
                }
            }
            Interaction::Hovered => {
                *material = button_materials.hovered;
            }
            Interaction::None => {
                *material = button_materials.normal;
            }
        }
    }
}

fn setup(
    mut commands: Commands,
    mut font_manager:ResMut<Assets<Font>>,
    asset_server: Res<AssetServer>,
    button_materials: Res<ButtonMaterials>,
) {
    let font = asset_server
    .load_sync(&mut font_manager,"assets/fonts/FiraCode-Light.ttf")
    .unwrap();

    let root = commands
        .spawn(UiCameraComponents::default())
        .spawn(NodeComponents {
                style: Style {
                    size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
                    flex_direction: FlexDirection::ColumnReverse,
                    justify_content: JustifyContent::FlexStart,
                    align_items: AlignItems::Center,
                    ..Default::default()
                },
                ..Default::default()
            }
        )
        .with(Root{})
        .current_entity().unwrap();

    let button = spawn_button_node(&mut commands, font,&button_materials,  "First button");
    commands.push_children(root, &[button]);
}

struct ButtonMaterials {
    normal: Handle<ColorMaterial>,
    hovered: Handle<ColorMaterial>,
}

impl FromResources for ButtonMaterials {
    fn from_resources(resources: &Resources) -> Self {
        let mut materials = resources.get_mut::<Assets<ColorMaterial>>().unwrap();
        ButtonMaterials {
            normal: materials.add(Color::rgb(0.02, 0.02, 0.02).into()),
            hovered: materials.add(Color::rgb(0.05, 0.05, 0.05).into()),
        }
    }
}

#[derive(Default)]
struct ButtonCount(u32);

struct Root;

fn spawn_button_node(
    commands: &mut Commands,
    font: Handle<Font>,
    mats: &Res<ButtonMaterials>,
    label: &str,
) -> Entity {
    commands
        .spawn(ButtonComponents {
                style: Style {
                    size: Size::new(Val::Px(300.0), Val::Px(65.0)),
                    // center button
                    margin: Rect::all(Val::Auto),
                    // horizontally center child text
                    justify_content: JustifyContent::Center,
                    // vertically center child text
                    align_items: AlignItems::Center,
                    ..Default::default()
                },
                material: mats.normal,
                ..Default::default()
            }
        )
        .with_children(|parent| {
            parent.spawn(TextComponents {
                text: Text {
                    value: label.to_string(),
                    style: TextStyle {
                        font_size: 28.0,
                        color: Color::rgb(1.0, 0.1, 0.1),
                    },
                    font:font,
                },
                ..Default::default()
            });
        })
        .current_entity().unwrap()
}

2
3

Closed by #386.