diff --git a/Cargo.toml b/Cargo.toml index ca29624ef92a2..7f30b88a057fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3354,6 +3354,17 @@ description = "An example to debug overflow and clipping behavior" category = "UI (User Interface)" wasm = true +[[example]] +name = "overflow_scroll" +path = "examples/ui/overflow_scroll.rs" +doc-scrape-examples = true + +[package.metadata.example.overflow_scroll] +name = "Overflow and ScrollPosition interaction" +description = "Shows the behavior of `ScrollPosition` for nodes with [`OverflowAxis::Visible`], [`OverflowAxis::Scroll`], and [`OverflowAxis::ScrollNoClip`]" +category = "UI (User Interface)" +wasm = true + [[example]] name = "relative_cursor_position" path = "examples/ui/relative_cursor_position.rs" diff --git a/crates/bevy_ui/src/layout/convert.rs b/crates/bevy_ui/src/layout/convert.rs index 53c03113b9b60..0c0276fd3dfac 100644 --- a/crates/bevy_ui/src/layout/convert.rs +++ b/crates/bevy_ui/src/layout/convert.rs @@ -265,7 +265,7 @@ impl From for taffy::style::Overflow { OverflowAxis::Visible => taffy::style::Overflow::Visible, OverflowAxis::Clip => taffy::style::Overflow::Clip, OverflowAxis::Hidden => taffy::style::Overflow::Hidden, - OverflowAxis::Scroll => taffy::style::Overflow::Scroll, + OverflowAxis::Scroll | OverflowAxis::ScrollNoClip => taffy::style::Overflow::Scroll, } } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 484acbd4af63e..189dfb396937f 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -310,12 +310,18 @@ with UI components as a child of an entity without UI components, your UI layout let scroll_position: Vec2 = maybe_scroll_position .map(|scroll_pos| { Vec2::new( - if style.overflow.x == OverflowAxis::Scroll { + if matches!( + style.overflow.x, + OverflowAxis::Scroll | OverflowAxis::ScrollNoClip + ) { scroll_pos.offset_x } else { 0.0 }, - if style.overflow.y == OverflowAxis::Scroll { + if matches!( + style.overflow.y, + OverflowAxis::Scroll | OverflowAxis::ScrollNoClip + ) { scroll_pos.offset_y } else { 0.0 diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index d81f80f8b9626..0c36e1312cdf6 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1185,6 +1185,30 @@ impl Overflow { y: OverflowAxis::Scroll, } } + + /// Scroll overflowing items on both axis without clipping them + pub const fn scroll_no_clip() -> Self { + Self { + x: OverflowAxis::ScrollNoClip, + y: OverflowAxis::ScrollNoClip, + } + } + + /// Scroll overflowing items on the x axis without clipping them + pub const fn scroll_x_no_clip() -> Self { + Self { + x: OverflowAxis::ScrollNoClip, + y: OverflowAxis::DEFAULT, + } + } + + /// Scroll overflowing items on the y axis without clipping them + pub const fn scroll_y_no_clip() -> Self { + Self { + x: OverflowAxis::DEFAULT, + y: OverflowAxis::ScrollNoClip, + } + } } impl Default for Overflow { @@ -1210,6 +1234,8 @@ pub enum OverflowAxis { Hidden, /// Scroll overflowing items. Scroll, + /// Scroll overflowing items without clipping overflown items. + ScrollNoClip, } impl OverflowAxis { @@ -1217,7 +1243,7 @@ impl OverflowAxis { /// Overflow is visible on this axis pub const fn is_visible(&self) -> bool { - matches!(self, Self::Visible) + matches!(self, Self::Visible | Self::ScrollNoClip) } } diff --git a/examples/README.md b/examples/README.md index 7b21d15da3ba0..3055c67624c5c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -557,6 +557,7 @@ Example | Description [Overflow](../examples/ui/overflow.rs) | Simple example demonstrating overflow behavior [Overflow Clip Margin](../examples/ui/overflow_clip_margin.rs) | Simple example demonstrating the OverflowClipMargin style property [Overflow and Clipping Debug](../examples/ui/overflow_debug.rs) | An example to debug overflow and clipping behavior +[Overflow and ScrollPosition interaction](../examples/ui/overflow_scroll.rs) | Shows the behavior of `ScrollPosition` for nodes with [`OverflowAxis::Visible`], [`OverflowAxis::Scroll`], and [`OverflowAxis::ScrollNoClip`] [Relative Cursor Position](../examples/ui/relative_cursor_position.rs) | Showcases the RelativeCursorPosition component [Render UI to Texture](../examples/ui/render_ui_to_texture.rs) | An example of rendering UI as a part of a 3D world [Scroll](../examples/ui/scroll.rs) | Demonstrates scrolling UI containers diff --git a/examples/ui/overflow_scroll.rs b/examples/ui/overflow_scroll.rs new file mode 100644 index 0000000000000..9f402eb25bd48 --- /dev/null +++ b/examples/ui/overflow_scroll.rs @@ -0,0 +1,152 @@ +//! Shows the distinction between [`Overflow::visible`], [`Overflow::scroll`], and [`Overflow::scroll_no_clip`]. +//! +//! Use [`KeyCode::ArrowLeft`] or [`KeyCode::ArrowRight`] to scroll the items + +use bevy::{ + app::{App, Startup, Update}, + asset::{AssetServer, Handle}, + color::Color, + core_pipeline::core_2d::Camera2d, + ecs::{ + bundle::Bundle, + children, + component::Component, + hierarchy::{ChildOf, Children}, + query::With, + spawn::{SpawnIter, SpawnRelated, SpawnableList}, + system::{Commands, Query, Res}, + }, + image::{Image, TextureAtlas, TextureAtlasLayout}, + input::{keyboard::KeyCode, ButtonInput}, + math::UVec2, + time::Time, + ui::{ + widget::{ImageNode, Text}, + AlignItems, AlignSelf, BorderColor, FlexDirection, JustifySelf, Node, Overflow, + ScrollPosition, UiRect, Val, + }, + DefaultPlugins, +}; + +/// Length of the sides of the texture +const TEXTURE_SIDES: u32 = 64; +/// Length of the sides of the image nodes +const NODE_SIDES: f32 = 64. * 2.; +/// Speed of scrolling in pixels per second +const SCROLL_SPEED: f32 = 320.; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, scroll) + .run(); +} + +#[derive(Component)] +/// Marks UI [`Node`] that can be scrolled +struct Scrollable; + +/// Spawns a scene with 3 UI nodes +/// +/// [`Overflow::visible`] allows items to be visible, even those out of the bounds of the node +/// [`Overflow::scroll`] clips items out of the bounds but allows scrolling using [`ScrollPosition`] +/// [`Overflow::scroll`] allows scrolling using [`ScrollPosition`] and keeps items out of bounds visible +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera2d); + + let image = asset_server.load("textures/food_kenney.png"); + let layout = TextureAtlasLayout::from_grid(UVec2::splat(TEXTURE_SIDES), 7, 6, None, None); + let layout_handle = asset_server.add(layout); + + commands.spawn(( + Node { + width: Val::Vw(80.), + flex_direction: FlexDirection::Column, + align_self: AlignSelf::Center, + justify_self: JustifySelf::Center, + ..Default::default() + }, + Children::spawn(ExampleNodes(image, layout_handle)), + )); +} + +/// Scrolls the UI nodes on [`KeyCode::ArrowRight`] or [`KeyCode::ArrowLeft`] inputs +fn scroll( + mut scrollables: Query<&mut ScrollPosition, With>, + key_codes: Res>, + time: Res