ScrollbarThumb does not respect Overflow::clip()
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 171
Description
## Bevy version and features
0.19.0
## What you did
Wrapped the scrollbar example in an Overflow::clip() node.
```rs
//! Demonstrations of scrolling and scrollbars.
use bevy::{
ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
input_focus::tab_navigation::{TabGroup, TabNavigationPlugin},
picking::hover::Hovered,
prelude::*,
ui_widgets::{ControlOrientation, Scrollbar, ScrollbarDragState, ScrollbarThumb},
};
fn main() {
App::new()
.add_plugins((DefaultPlugins, TabNavigationPlugin))
.insert_resource(UiScale(1.25))
.add_systems(Startup, setup_view_root)
.add_systems(Update, update_scrollbar_thumb)
.run();
}
fn setup_view_root(mut commands: Commands) {
let camera = commands.spawn((Camera::default(), Camera2d)).id();
commands.spawn((
Node {
display: Display::Flex,
flex_direction: FlexDirection::Column,
position_type: PositionType::Absolute,
left: px(0),
top: px(0),
right: px(0),
bottom: px(0),
padding: UiRect::all(px(3)),
row_gap: px(6),
..Default::default()
},
BackgroundColor(Color::srgb(0.1, 0.1, 0.1)),
UiTargetCamera(camera),
TabGroup::default(),
Children::spawn((
Spawn(Text::new("Scrolling")),
// BUG: wrap the scroll demo in a shorter `overflow: clip` box.
Spawn((
Node {
height: px(90),
border: px(2).all(),
overflow: Overflow::clip(),
..default()
},
BorderColor::all(Color::WHITE),
children![scroll_area_demo()],
)),
)),
));
}
/// Create a scrolling area.
///
/// The "scroll area" is a container that can be scrolled. It has a nested structure which is
/// three levels deep:
/// - The outermost node is a grid that contains the scroll area and the scrollbars.
/// - The scroll area is a flex container that contains the scrollable content. This
/// is the element that has the `overflow: scroll` property.
/// - The scrollable content consists of the elements actually displayed in the scrolling area.
fn scroll_area_demo() -> impl Bundle {
(
// Frame element which contains the scroll area and scrollbars.
Node {
display: Display::Grid,
width: px(200),
height: px(150),
grid_template_columns: vec![RepeatedGridTrack::flex(1, 1.), RepeatedGridTrack::auto(1)],
grid_template_rows: vec![RepeatedGridTrack::flex(1, 1.), RepeatedGridTrack::auto(1)],
row_gap: px(2),
column_gap: px(2),
..default()
},
Children::spawn((SpawnWith(|parent: &mut RelatedSpawner| {
// The actual scrolling area.
// Note that we're using `SpawnWith` here because we need to get the entity id of the
// scroll area in order to set the target of the scrollbars.
let scroll_area_id = parent
.spawn((
Node {
display: Display::Flex,
flex_direction: FlexDirection::Column,
padding: UiRect::all(px(4)),
overflow: Overflow::scroll(),
..default()
},
BackgroundColor(colors::GRAY1.into()),
ScrollPosition(Vec2::new(0.0, 10.0)),
Children::spawn((
// The actual content of the scrolling area
Spawn(text_row("Alpha Wolf")),
Spawn(text_row("Beta Blocker")),
Spawn(text_row("Delta Sleep")),
Spawn(text_row("Gamma Ray")),
Spawn(text_row("Epsilon Eridani")),
Spawn(text_row("Zeta Function")),
Spawn(text_row("Lambda Calculus")),
Spawn(text_row("Nu Metal")),
Spawn(text_row("Pi Day")),
Spawn(text_row("Chi Pants")),
Spawn(text_row("Psi Powers")),
Spawn(text_row("Omega Fatty Acid")),
)),
))
.id();
// Vertical scrollbar
parent.spawn((
Node {
min_width: px(8),
grid_row: GridPlacement::start(1),
grid_column: GridPlacement::start(2),
..default()
},
Scrollbar {
orientation: ControlOrientation::Vertical,
target: scroll_area_id,
min_thumb_length: 8.0,
},
Children::spawn(Spawn((
Hovered::default(),
BackgroundColor(colors::GRAY2.into()),
BorderColor::all(colors::GRAY3),
ScrollbarThumb {
border_radius: BorderRadius::all(px(4)),
border: px(1).all(),
},
))),
));
// Horizontal scrollbar
parent.spawn((
Node {
min_height: px(8),
grid_row: GridPlacement::start(2),
grid_column: GridPlacement::start(1),
..default()
},
Scrollbar {
orientation: ControlOrientation::Horizontal,
target: scroll_area_id,
min_thumb_length: 8.0,
},
Children::spawn(Spawn((
Hovered::default(),
BackgroundColor(colors::GRAY2.into()),
BorderColor::all(colors::GRAY3),
ScrollbarThumb {
border_radius: BorderRadius::all(px(4)),
border: px(1).all(),
},
))),
));
}),)),
)
}
/// Create a list row
fn text_row(caption: &str) -> impl Bundle {
(
Text::new(caption),
TextFont {
font_size: FontSize::Px(14.0),
..default()
},
)
}
// Update the color of the scrollbar thumb.
fn update_scrollbar_thumb(
mut q_thumb: Query<
(&mut BackgroundColor, &Hovered, &ScrollbarDragState),
(
With,
Or<(Changed, Changed)>,
),
>,
) {
for (mut thumb_bg, Hovered(is_hovering), drag) in q_thumb.iter_mut() {
let color: Color = if *is_hovering || drag.dragging {
// If hovering, use a lighter color
colors::GRAY4
} else {
// Default color for the slider
colors::GRAY2
}
.into();
if thumb_bg.0 != color {
// Update the color of the thumb
thumb_bg.0 = color;
}
}
}
mod colors {
use bevy::color::Srgba;
pub const GRAY1: Srgba = Srgba::new(0.224, 0.224, 0.243, 1.0);
pub const GRAY2: Srgba = Srgba::new(0.486, 0.486, 0.529, 1.0);
pub const GRAY3: Srgba = Srgba::new(0.71, 0.71, 0.772, 1.0);
pub const GRAY4: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0);
}
```
This is the diff to the official example:
```diff
39c39,52
< Children::spawn((Spawn(Text::new("Scrolling")), Spawn(scroll_area_demo()))),
---
> Children::spawn((
> Spawn(Text::new("Scrolling")),
> // BUG: wrap the scroll demo in a shorter `overflow: clip` box.
> Spawn((
> Node {
> height: px(90),
> border: px(2).all(),
> overflow: Overflow::clip(),
> ..default()
> },
> BorderColor::all(Color::WHITE),
> children![scroll_area_demo()],
> )),
> )),
```
## What went wrong
The scrollbar thumbs are rendered outside of the Overflow::clip() node:
Contributor guide
Research direction
Reproduce the modified official scrolling and scrollbar example with the outer Node using Overflow::clip() and compare it with the unmodified example. Start by tracing how ScrollbarThumb is laid out and rendered relative to the clipping node. Done means the scrollbar thumbs remain clipped within the outer node without breaking scrolling or scrollbar interaction.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 62/100