Skip to content

Create a new OverflowAxis that allows scrolling while keeping overflown items visible #19773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

hukasu
Copy link
Contributor

@hukasu hukasu commented Jun 21, 2025

Objective

If you want to create a node that has a long list of items, have all of them visible and scroll through them, you would need to fiddle with Node::left or Node::right

Solution

Create OverflowAxis::ScrollNoClip that keeps all items visible and allows you to scroll using ScrollPosition

Showcase

overflow_scroll example
image

image

image

@hukasu hukasu added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets M-Needs-Release-Note Work that should be called out in the blog due to impact labels Jun 21, 2025
Copy link
Contributor

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@hukasu hukasu added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Jun 21, 2025
@hukasu hukasu mentioned this pull request Jun 21, 2025
@viridia
Copy link
Contributor

viridia commented Jun 22, 2025

you would need to fiddle with Node::left or Node::right

Why is that so bad? I think you need a clearer justification for this change.

@hukasu
Copy link
Contributor Author

hukasu commented Jun 22, 2025

Using ScrollPosition you don't need to check for the bounds, as it is already dealt with by bevy

@alice-i-cecile alice-i-cecile added the X-Contentious There are nontrivial implications that should be thought through label Jun 22, 2025
@ickshonpe
Copy link
Contributor

I'm not very convinced either. The overflow implementation isn't complete, overflow-scroll is meant to display scrollbars. Once we implement scroll bars, a "ScrollNoClip" variant doesn't make any sense.

@alice-i-cecile alice-i-cecile added X-Controversial There is active debate or serious implications around merging this PR S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed X-Contentious There are nontrivial implications that should be thought through S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 22, 2025
@hukasu
Copy link
Contributor Author

hukasu commented Jun 22, 2025

The scrollbars will be required by all ui nodes, not opt-in?

@hukasu
Copy link
Contributor Author

hukasu commented Jun 22, 2025

Looking at the screenshots you will see that all 3 have a ui node that is 128x128, using ScrollNoClop I can just use ScrollPosition to scroll until it starts clamping, if I were to use Visible and move using Node::left or Node::right I would need to query for the window size, calculate the offset by hand, and then clamp, all things that are already done on Scroll

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

Updated the example to include a border to show the UI Node

@alice-i-cecile alice-i-cecile added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jun 23, 2025
@viridia
Copy link
Contributor

viridia commented Jun 23, 2025

Looking at the screenshot, it seems like you are trying to create a diegetic user interface. If that's the case, then perhaps bevy_ui isn't the right framework? Maybe you want to use actual pickable 3d objects?

In any case, this seems like a very niche use case, and given that there's a (admittedly more complex) workaround, I'm not sure it's worth it adding to Bevy engine. (There's another argument I could make, but this involves an etymological argument about the definition of the word "scroll" which I'd rather not get into.)

On the question of scrollbars: I've been assuming that games will want to customize the look and feel of scrollbars, so I've been wary of the idea of automatically adding scrollbars to scrolling entities.

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

my idea is this zelda ui

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

@alice-i-cecile alice-i-cecile added S-Needs-Design This issue requires design work to think about how it would best be accomplished and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 23, 2025
@ickshonpe
Copy link
Contributor

ickshonpe commented Jun 23, 2025

On the question of scrollbars: I've been assuming that games will want to customize the look and feel of scrollbars, so I've been wary of the idea of automatically adding scrollbars to scrolling entities.

It's not so much about about displaying the scrollbars themselves. It's about controlling the non-visual layout behaviour like:

  • Whether scrollbars should occupy space in the space in the layout or float above the container.
  • Automatically allocating space for the scrollbar gutters when content overflows.
  • Stable scroll positions with anchoring so content doesn't shift when it changes size.

Problems which our current scrolling implementation just completely ignores 😓

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

i don't want this discussion to devolve into a discussion about scrollbars, but i would like for scrollbars to be able to be placed anywhere, even on a separate UI hierarchy, probably using relationships, i.e. TrackingScrollOf, ScrollTrackedBy

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

and back to this PR, what alternative do y'all suggest then? Overflow::scroll with OverflowMargin set to u32::MAX?

@viridia
Copy link
Contributor

viridia commented Jun 23, 2025

The ScrollPosition component is actually a relatively recent addition, before that I was doing scrolling using absolute positioning. I used three layers of nesting:

  • An outer "scroll container" entity.
  • An inner "scroll content" entity absolutely positioned within it.
  • The actual scrollable items within the content entity.

Using ComputedNode it's relatively easy to measure the size of both the outer and inner parts.

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

i just don't see it being ergonomic redoing all the math that is already done by ScrollPosition

@ickshonpe
Copy link
Contributor

ickshonpe commented Jun 23, 2025

and back to this PR, what alternative do y'all suggest then? Overflow::scroll with OverflowMargin set to u32::MAX?

There are lots of ways to construct this without using overflow, the problem with CSS is there are too many ways to do everything. Adapted from your example:

        // index of the element inside the white border
        let x = (6 * 7 - 1) as f32;
        world.spawn((
            Node {
                width: Val::Percent(100.),
                flex_direction: FlexDirection::Column,
                align_items: AlignItems::Center,
                ..Default::default()
            },
            children![
                Text::new("no overflow"),
                (
                    Scrollable,
                    Node {
                        width: Val::Px(NODE_SIDES),
                        height: Val::Px(NODE_SIDES),
                        flex_direction: FlexDirection::Row,
                        border: UiRect::all(Val::Px(2.)),
                        overflow: Overflow::visible(),
                        ..Default::default()
                    },
                    BorderColor::all(Color::WHITE),
                    children![(
                        Node {
                            position_type: bevy::ui::PositionType::Absolute,
                            width: Val::Px(NODE_SIDES),
                            height: Val::Px(NODE_SIDES),
                            ..Default::default()
                        },
                        UiTransform::from_translation(Val2::new(
                            Val::Percent(x * -100.),
                            Val::Px(0.),
                        )),
                        Children::spawn(SpawnIter(
                            (0..(6 * 7))
                                .map(|id| (
                                    Node {
                                        position_type: bevy::ui::PositionType::Absolute,
                                        width: Val::Px(NODE_SIDES),
                                        height: Val::Px(NODE_SIDES),
                                        ..Default::default()
                                    },
                                    UiTransform::from_translation(Val2::new(
                                        Val::Percent(id as f32 * 100.),
                                        Val::Px(0.),
                                    )),
                                    ImageNode {
                                        image: self.0.clone(),
                                        texture_atlas: Some(TextureAtlas {
                                            layout: self.1.clone(),
                                            index: id,
                                        }),
                                        image_mode: NodeImageMode::Stretch,
                                        ..Default::default()
                                    },
                                ))
                                .collect::<Vec<_>>()
                                .into_iter(),
                        ))
                    )]
                )
            ],
            ChildOf(entity),
        ));

Screenshot 2025-06-23 224614

It's a bit complicated because I hacked it together quickly but the construction can be simplified a lot with some effort. There's no need for the absolute positioning and the transforms on the ImageNode children for instance, they could just be wrapped by a single parent.

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

then bevy_ui has to have a very tight coupling with the behavior of css?

@viridia
Copy link
Contributor

viridia commented Jun 23, 2025

then bevy_ui has to have a very tight coupling with the behavior of css?

Not exactly. Bevy's relationship with CSS is complicated:

  • A lot of devs are coming from the web world and want to work with layout concepts that they know.
  • Bevy uses Taffy for it's layout engine, and Taffy is explicitly supposed to be CSS-compatible.
  • However, not every dev likes the CSS model, look at something like morphorm which is very different.
  • So ideally Bevy would offer a choice of different layout strategies.

@hukasu
Copy link
Contributor Author

hukasu commented Jun 23, 2025

then is it really that bad extending the CSS-like OverflowAxis to include this one extra variant? i see this as the most concise way to achieve the UI that i am trying to emulate for the usage example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants