Skip to content

ScrollPositions does not work on Node with flex_direction set to RowReverse #17129

@hukasu

Description

@hukasu

Bevy version

v0.15

[Optional] Relevant system information

2025-01-03T20:16:22.418810Z  INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux 24.11 NixOS", kernel: "6.6.63", cpu: "Intel(R) Core(TM) i7-14700KF", core_count: "20", memory: "31.2 GiB" }
2025-01-03T20:16:22.566379Z  INFO bevy_render::renderer: AdapterInfo { name: "NVIDIA GeForce RTX 4060 Ti", vendor: 4318, device: 10243, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "560.35.03", backend: Vulkan }

What you did

Created a Node with flex_direction set to RowReverse or ColumnReverse and scrolling enabled, updated the value of the offset in ScrollPosition.

What went wrong

Offsets of ScrollPosition are clamped to 0, 0 as if already scrolled to the end, when trying to scroll either way.

Additional information

use bevy::{
    app::{App, Startup, Update},
    color::{palettes, Color},
    prelude::{
        BuildChildren, Camera2d, ChildBuild, ChildBuilder, Commands, Component, Local, Query, Text,
        With, Without,
    },
    text::TextColor,
    ui::{BackgroundColor, FlexDirection, Node, Overflow, ScrollPosition, Val},
    DefaultPlugins,
};

#[derive(Component)]
struct Row;
#[derive(Component)]
struct Column;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, (scroll_x, scroll_y))
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2d);

    let colors: &[Color] = &[
        palettes::tailwind::BLUE_100.into(),
        palettes::tailwind::GREEN_100.into(),
    ];

    let row_spawner = |parent: &mut ChildBuilder, flex_direction, max_width| {
        parent
            .spawn((
                Row,
                Node {
                    max_width,
                    flex_direction,
                    overflow: Overflow::scroll_x(),
                    ..Default::default()
                },
            ))
            .with_children(|parent| {
                for i in 0..50 {
                    parent
                        .spawn((
                            Node {
                                min_width: Val::Px(50.),
                                min_height: Val::Px(50.),
                                justify_content: bevy::ui::JustifyContent::SpaceEvenly,
                                align_content: bevy::ui::AlignContent::SpaceEvenly,
                                ..Default::default()
                            },
                            BackgroundColor(colors[i % 2]),
                        ))
                        .with_child((Text::new(i.to_string()), TextColor(Color::BLACK)));
                }
            });
    };
    let col_spawner = |parent: &mut ChildBuilder, flex_direction, max_height| {
        parent
            .spawn((
                Column,
                Node {
                    max_height,
                    flex_direction,
                    overflow: Overflow::scroll_y(),
                    ..Default::default()
                },
            ))
            .with_children(|parent| {
                for i in 0..50 {
                    parent
                        .spawn((
                            Node {
                                min_width: Val::Px(50.),
                                min_height: Val::Px(50.),
                                justify_content: bevy::ui::JustifyContent::SpaceEvenly,
                                align_content: bevy::ui::AlignContent::SpaceEvenly,
                                ..Default::default()
                            },
                            BackgroundColor(colors[i % 2]),
                        ))
                        .with_child((Text::new(i.to_string()), TextColor(Color::BLACK)));
                }
            });
    };
    commands
        .spawn(Node {
            flex_direction: FlexDirection::Column,
            ..Default::default()
        })
        .with_children(|parent| {
            row_spawner(parent, FlexDirection::Row, Val::Vw(50.));
            row_spawner(parent, FlexDirection::RowReverse, Val::Vw(50.));
            parent.spawn(Node::default()).with_children(|parent| {
                col_spawner(parent, FlexDirection::Column, Val::Vh(50.));
                col_spawner(parent, FlexDirection::ColumnReverse, Val::Vh(50.));
            });
        });
}

fn scroll_x(
    mut rows: Query<&mut ScrollPosition, (With<Row>, Without<Column>)>,
    mut local: Local<u64>,
) {
    let f: fn(&mut f32) = if *local & 0x100 != 0 {
        |x| *x -= 2.
    } else {
        |x| *x += 2.
    };
    for mut row in rows.iter_mut() {
        f(&mut row.offset_x);
    }
    *local += 1;
}

fn scroll_y(
    mut columns: Query<&mut ScrollPosition, (With<Column>, Without<Row>)>,
    mut local: Local<u64>,
) {
    let f: fn(&mut f32) = if *local & 0x100 != 0 {
        |x| *x -= 2.
    } else {
        |x| *x += 2.
    };
    for mut column in columns.iter_mut() {
        f(&mut column.offset_y);
    }
    *local += 1;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-UIGraphical user interfaces, styles, layouts, and widgetsC-BugAn unexpected or incorrect behaviorD-StraightforwardSimple bug fixes and API improvements, docs, test and examplesS-Needs-InvestigationThis issue requires detective work to figure out what's going wrong

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions