Skip to content

Text wrapping should work with a percent width #1490

Closed

Description

@jtran

Bevy version

v0.4.0

Operating system & version

macOS 10.15.7

What you did

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup.system())
        .run();
}

fn setup(commands: &mut Commands, asset_server: Res<AssetServer>) {
    commands
        .spawn(CameraUiBundle::default())
        .spawn(TextBundle {
            text: Text {
                value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas auctor, nunc ac faucibus fringilla.".to_string(),
                font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                style: TextStyle {
                    font_size: 60.0,
                    color: Color::WHITE,
                    ..Default::default()
                },
            },
            style: Style {
                position_type: PositionType::Absolute,
                position: Rect {
                    left: Val::Px(0.0),
                    top: Val::Px(0.0),
                    ..Default::default()
                },
                max_size: Size::new(Val::Percent(50.0), Val::Percent(100.0)),
                ..Default::default()
            },
            ..Default::default()
        });
}

What you expected to happen

I expected it to wrap the text at half the window width.

What actually happened

It doesn't wrap the text. It runs right up to the right edge of the window.

Additional information

It seems that setting a max_size with Val::Px() works, but percent doesn't.

Activity

  1. added
    C-BugAn unexpected or incorrect behavior
    D-TrivialNice and easy! A great choice to get started with Bevy
    A-UIGraphical user interfaces, styles, layouts, and widgets
    on Feb 21, 2021
  2. djeedai commented on Aug 30, 2022

    @djeedai
    Contributor

    Relevant code:

    /// Defines how `min_size`, `size`, and `max_size` affects the bounds of a text
    /// block.
    pub fn text_constraint(min_size: Val, size: Val, max_size: Val, scale_factor: f64) -> f32 {
    // Needs support for percentages
    match (min_size, size, max_size) {
    (_, _, Val::Px(max)) => scale_value(max, scale_factor),
    (Val::Px(min), _, _) => scale_value(min, scale_factor),
    (Val::Undefined, Val::Px(size), Val::Undefined) | (Val::Auto, Val::Px(size), Val::Auto) => {
    scale_value(size, scale_factor)
    }
    _ => f32::MAX,
    }
    }

  3. FaneTheEternal commented on Sep 17, 2022

    @FaneTheEternal

    Is there any news on this issue?

  4. jkb0o commented on Oct 24, 2022

    @jkb0o
    Contributor

    The workaround is to split text into the words and let Taffy layout it, you can achieve even text-aligment: justify or any rich text effect with this approach:

    text_aligment

    I've dive a bit into code (thank you @djeedai for pointing), and it looks difficult to fix this the issue without rewriting a bit of code. Taffy is pretty good at layouting (except for maybe #5834), but it is best when it knows atomic element sizes (the size of each word for example). It calculates all layout into a single pass and I believe it is better to keep it as it is. It could be an idea to rework TextBundle to let Taffy layout text as well: as far as I understend, ui already batches text nodes and there is shouldn't be lot of performance lost. Custom plugin would do the job as well.

    There is a gist (https://gist.github.com/jkb0o/58ee5980ef6077b39f91979f7f19cddc), project archieve (bevy_example_text_aligment.zip) and inline code:

    //! This example illustrates how you can cheat text layout with using bevy (with taffy)
    //! It works with latest bevy from the main branch (at the date of writing)
    //! ref 6aa2dce
    //! https://github.com/bevyengine/bevy/tree/6aa2dce
    //! 
    //! Resize the window to see the effect
    
    use bevy::prelude::*;
    
    const FONT_SIZE: f32 = 25.0;
    
    fn main() {
        App::new()
            .add_plugins(DefaultPlugins)
            .add_startup_system(setup)
            .run();
    }
    
    fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
        commands.spawn(Camera2dBundle::default());
    
        // I will split this text into the words and let Taffy to
        // layout it with FlexWrap::Wrap
        let text = "Lorem ipsum dolor sit amet consectetur adipiscing elit. Maecenas auctor, nunc ac faucibus fringilla."
            .to_string();
    
        commands.spawn(NodeBundle {
            style: Style {
                size: Size::new(Val::Percent(50.), Val::Undefined),
                // you can achieve text-align properties using justify content:
                // - JustifyContent::Center for `center`
                // - JustifyContent::FlexStart for `left`
                // - JustifyContent::FlexEnd for `right`
                // - JustifyContent::SpaceBetween for `justify` 
                //   (it looks better when add extra space at the end of the string.
                //   even better if you add empty node with `flex_grow: 1`)
    
                justify_content: JustifyContent::SpaceBetween,
                flex_wrap: FlexWrap::Wrap,
                ..default()
            },
            ..default()
        }).with_children(|commands| {
            for word in text.split(" ") {
                commands.spawn((
                    TextBundle::from_section(
                        word.to_string(),
                        TextStyle {
                            font: asset_server.load("FiraMono-Medium.ttf"),
                            font_size: FONT_SIZE,
                            color: Color::WHITE,
                        },
                    )
                    .with_text_alignment(TextAlignment::TOP_CENTER)
                    .with_style(Style {
                        // this is required becouse of the bevy bug
                        // https://github.com/bevyengine/bevy/issues/5834
                        max_size: Size::new(Val::Undefined, Val::Px(FONT_SIZE)),
    
                        // this is the size of the spaces between words
                        margin: UiRect::all(Val::Px(3.)),
                        ..default()
                    }),
                ));
            }
            // comment it for everithing except `justify` text aligment
            commands.spawn(NodeBundle {
                style: Style {
                    flex_grow: 1.,
                    ..default()
                },
                ..default()
            });
        });
    }
  5. janos-r commented on Oct 31, 2022

    @janos-r

    @jkb0o
    Thank you! I made it work with

    flex_wrap: FlexWrap::WrapReverse, 
    align_content: AlignContent::FlexStart,
    

    Because for me (not sure what default that is) everything starts from the bottom left, so without this, the text spawns from the bottom left and wraps up.

    Last issue here is... how would you update the text box? Since it's not one text element I can just insert a component to. I was hoping to fill this box through a resource. I guess I can still have a global resource text... but some system has to somehow pick the entity? and every time despawn all its text children and spawn this way new ones? Never deleted and respawned children on a queried something... Thanks for any tip...

  6. janos-r commented on Nov 5, 2022

    @janos-r

    Ok, I finally got back to it.
    This worked on my first try. And I guess in a similar way I would spawn new children (new text) for the entity (text container). I have to say tho, this feels a little weird. To make such mutating operations on Entity, that for some reason is in the Query without either & or &mut. Just that seams to me unusual in bevy (maybe I don't undestand something properly). And also the &Children get despawned, even though they are just borrowed.

    Also in here https://bevy-cheatbook.github.io/features/parent-child.html#known-pitfalls I found that I also should call the despawning separately from the removing. I hope that is still the right way how to do it.

    fn despawn_text (
        mut commands: Commands,
        text_box: Query<(Entity, &Children), With<TextBox>>
    ) {
        let (entity, children) = text_box.single();
        commands.entity(entity).remove_children(children);
        for child in children {
            commands.entity(*child).despawn_recursive();
        }
    }

    Please let me know if you have some clarifying comments on this.
    But all in all I can work with this and thank you very much for the help :)

  7. djeedai commented on Dec 23, 2022

    @djeedai
    Contributor

    To make such mutating operations on Entity, that for some reason is in the Query without either & or &mut.

    Entity is an index; think of it as u32 to simplify. commands.entity(entity) returns a command queue for that particular entity, where mutation commands are recorded. Therefore there's no mutation happening to the entity inside that system. Later at the end of the stage the commands are applied by Bevy automatically and the mutations occur, somewhere else. So this is consistent with Rust rules, and is a common pattern for parallel computing (record mutations in parallel, apply them later sequentially).

  8. added a commit that references this issue on Mar 4, 2023
  9. added a commit that references this issue on Apr 17, 2023
  10. ickshonpe commented on May 4, 2023

    @ickshonpe
    Contributor

    fixed by #7779

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-UIGraphical user interfaces, styles, layouts, and widgetsC-BugAn unexpected or incorrect behaviorD-TrivialNice and easy! A great choice to get started with Bevy

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions