bevy/examples/ui/drag_to_scroll.rs
ickshonpe d195116426
Improved UI scrolling support and bug fixes (#20093)
# Objective

#### Goals
* Stop layout updates from overwriting `ScrollPosition`.
* Make `ScrollPosition` respect scale factor.
* Automatically allocate space for a scrollbar on an axis when
`OverflowAxis::Scroll` is set.
 
#### Non-Goals
* Overflow-auto support (I was certain Taffy had this already, but
apparently I was hallucinating).
* Implement any sort of scrollbar widgets.
* Stability (not needed because no overflow-auto support).
* Maybe in the future we could make a `ScrollbarWidth` enum to more
closely match the CSS API with its auto/narrow/none options. For now
`scrollbar_width` is just an `f32` which matches Taffy's API.

## Solution

* Layout updates no longer overwrite `ScrollPosition`'s value.
* Added the field `scrollbar_width: f32` to `Node`. This is sent to
`Taffy` which will automatically allocate space for scrollbars with this
width in the layout as needed.
* Added the fields `scrollbar_width: f32` and `scroll_position: Vec2` to
`ComputedNode`. These are updated automatically during layout.
* `ScrollPosition` now respects scale factor.
* `ScrollPosition` is no longer automatically added to every UI node
entity by `ui_layout_system`. If every node needs it, it should just be
required by (or be a field on) `Node`. Not sure if that's necessary or
not.

## Testing
For testing you can look at:
* The `scrollbars` example, which should work as before.
* The new example `drag_to_scroll`.
* The `scroll` example which automatically allocates space for
scrollbars on the left hand scrolling list. Did not implement actual
scrollbars so you'll just see a gap atm.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-07-15 17:33:04 +00:00

121 lines
4.2 KiB
Rust

//! This example tests scale factor, dragging and scrolling
use bevy::color::palettes::css::RED;
use bevy::prelude::*;
#[derive(Component)]
struct DragNode;
#[derive(Component)]
struct ScrollableNode;
#[derive(Component)]
struct TileColor(Color);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
#[derive(Component)]
struct ScrollStart(Vec2);
fn setup(mut commands: Commands) {
let w = 60;
let h = 40;
commands.spawn(Camera2d);
commands.insert_resource(UiScale(0.5));
commands
.spawn((
Node {
width: Val::Percent(100.),
height: Val::Percent(100.),
overflow: Overflow::scroll(),
..Default::default()
},
ScrollPosition(Vec2::ZERO),
ScrollableNode,
ScrollStart(Vec2::ZERO),
))
.observe(
|
drag: On<Pointer<Drag>>,
ui_scale: Res<UiScale>,
mut scroll_position_query: Query<(
&mut ScrollPosition,
&ScrollStart),
With<ScrollableNode>,
>| {
if let Ok((mut scroll_position, start)) = scroll_position_query.single_mut() {
scroll_position.0 = (start.0 - drag.distance / ui_scale.0).max(Vec2::ZERO);
}
},
)
.observe(
|
on: On<Pointer<DragStart>>,
mut scroll_position_query: Query<(
&ComputedNode,
&mut ScrollStart),
With<ScrollableNode>,
>| {
if on.target() != on.original_target() {
return;
}
if let Ok((computed_node, mut start)) = scroll_position_query.single_mut() {
start.0 = computed_node.scroll_position * computed_node.inverse_scale_factor;
}
},
)
.with_children(|commands| {
commands
.spawn(Node {
display: Display::Grid,
grid_template_rows: RepeatedGridTrack::px(w as i32, 100.),
grid_template_columns: RepeatedGridTrack::px(h as i32, 100.),
..Default::default()
})
.with_children(|commands| {
for y in 0..h {
for x in 0..w {
let tile_color = if (x + y) % 2 == 1 {
let hue = ((x as f32 / w as f32) * 270.0) + ((y as f32 / h as f32) * 90.0);
Color::hsl(hue, 1., 0.5)
} else {
Color::BLACK
};
commands
.spawn((
Node {
grid_row: GridPlacement::start(y + 1),
grid_column: GridPlacement::start(x + 1),
..Default::default()
},
Pickable {
should_block_lower: false,
is_hoverable: true,
},
TileColor(tile_color),
BackgroundColor(tile_color),
))
.observe(|on_enter: On<Pointer<Over>>, mut query: Query<&mut BackgroundColor>, | {
if let Ok(mut background_color) = query.get_mut(on_enter.target()) {
background_color.0 = RED.into();
}
})
.observe(|on_enter: On<Pointer<Out>>, mut query: Query<(&mut BackgroundColor, &TileColor)>,| {
if let Ok((mut background_color, tile_color)) = query.get_mut(on_enter.target()) {
background_color.0 = tile_color.0;
}
});
}
}
});
});
}