Compare commits

..

3 Commits
0.3.1 ... main

Author SHA1 Message Date
Piotr Siuszko 17d4cfb263 0.4 2025-05-07 10:38:31 +02:00
Piotr Siuszko 570d00d99a Format fix 2025-01-27 11:15:43 +01:00
Piotr Siuszko bf2f27231a Docs, default scroll view node function, updated sample 2025-01-27 10:56:04 +01:00
5 changed files with 109 additions and 112 deletions

View File

@ -1,5 +1,22 @@
# CHANGELOG # CHANGELOG
## [0.4.0]
## Changed
- Updated to Bevy 0.16
## [0.3.2]
## Added
- `scroll_view_node` function with default values for `ScrollView` node
- documentation to the code
## Fixed
- `ScrollableContent` default Node values fix
## [0.3.1] ## [0.3.1]
## Fixed ## Fixed

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bevy_simple_scroll_view" name = "bevy_simple_scroll_view"
version = "0.3.1" version = "0.4.0"
edition = "2021" edition = "2021"
exclude = [".github/", "wasm/", "record.gif"] exclude = [".github/", "wasm/", "record.gif"]
categories = ["game-development", "gui"] categories = ["game-development", "gui"]
@ -11,14 +11,17 @@ license = "MIT OR Apache-2.0"
description = "Simple to use plugin implementing ScrollView into Bevy engine." description = "Simple to use plugin implementing ScrollView into Bevy engine."
[dependencies.bevy] [dependencies.bevy]
version = "0.15" version = "0.16"
default-features = false default-features = false
features = ["bevy_ui", "bevy_asset", "bevy_text"] features = ["bevy_ui", "bevy_asset", "bevy_text"]
[dev-dependencies.bevy] [dev-dependencies.bevy]
version = "0.15" version = "0.16"
default-features = true default-features = true
[features] [features]
default = [] default = []
extra_logs = [] extra_logs = ["bevy/bevy_log"]
[lints.rust]
missing_docs = "warn"

View File

@ -36,6 +36,7 @@ Please keep PRs small and scoped to a single feature or fix.
Bevy version | crate version Bevy version | crate version
--- | --- --- | ---
0.16 | 0.4
0.15 | 0.3 0.15 | 0.3
0.14 | 0.2 0.14 | 0.2
0.13 | 0.1 0.13 | 0.1

View File

@ -1,135 +1,84 @@
#![allow(missing_docs)]
use bevy::picking::events::{Pointer, Released};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_simple_scroll_view::*; use bevy_simple_scroll_view::*;
const CLR_1: Color = Color::srgb(0.168, 0.168, 0.168); const BG_COLOR: BackgroundColor = BackgroundColor(Color::srgb(0.168, 0.168, 0.168));
const CLR_2: Color = Color::srgb(0.109, 0.109, 0.109); const BG_COLOR_2: BackgroundColor = BackgroundColor(Color::srgb(0.109, 0.109, 0.109));
const CLR_3: Color = Color::srgb(0.569, 0.592, 0.647); const CLR_3: Color = Color::srgb(0.569, 0.592, 0.647);
const CLR_4: Color = Color::srgb(0.902, 0.4, 0.004); const TEXT_COLOR: TextColor = TextColor(Color::srgb(0.902, 0.4, 0.004));
fn main() { fn main() {
App::new() App::new()
.add_plugins((DefaultPlugins, ScrollViewPlugin)) .add_plugins((DefaultPlugins, ScrollViewPlugin))
.add_systems(Startup, prepare) .add_systems(Startup, setup)
.add_systems(Update, reset_scroll)
.run(); .run();
} }
fn prepare(mut commands: Commands) { fn setup(mut commands: Commands) {
let base_node = Node {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
min_width: Val::Px(200.0),
margin: UiRect::all(Val::Px(10.0)),
border: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(15.0)),
..default()
};
commands.spawn(Camera2d); commands.spawn(Camera2d);
commands commands
.spawn(( .spawn((
BackgroundColor(CLR_1), BG_COLOR,
Node { Node {
width: Val::Percent(100.0), width: Val::Percent(100.0),
height: Val::Percent(100.0), height: Val::Percent(100.0),
padding: UiRect::all(Val::Px(15.0)),
..default() ..default()
}, },
)) ))
.with_children(|p| { .with_children(|p| {
p.spawn(Node { p.spawn(Node {
width: Val::Percent(20.0), width: Val::Percent(20.0),
margin: UiRect::all(Val::Px(10.0)), margin: UiRect::all(Val::Px(15.0)),
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
..default() ..default()
}) })
.with_children(|p| { .with_children(|p| {
for btn_action in [ScrollButton::MoveToTop, ScrollButton::MoveToBottom] { p.spawn((Text::new("Scroll to:"), TEXT_COLOR));
p.spawn(( let btn = (base_node.clone(), BG_COLOR_2, Button);
Node { p.spawn(btn.clone())
margin: UiRect::all(Val::Px(15.0)), .observe(scroll_to_top)
padding: UiRect::all(Val::Px(15.0)), .with_child((Text::new("top"), TEXT_COLOR));
max_height: Val::Px(100.0), p.spawn(btn)
border: UiRect::all(Val::Px(3.0)), .observe(scroll_to_bottom)
align_items: AlignItems::Center, .with_child((Text::new("bottom"), TEXT_COLOR));
..default()
},
BackgroundColor(CLR_2),
BorderColor(CLR_4),
Button,
btn_action,
))
.with_children(|p| {
p.spawn((
Text::new(format!("{:#?}", btn_action)),
TextFont {
font_size: 25.0,
..default()
},
TextColor(CLR_4),
));
});
}
}); });
p.spawn(( p.spawn((
Node { Node {
width: Val::Percent(80.0), width: Val::Percent(80.0),
margin: UiRect::all(Val::Px(15.0)), margin: UiRect::all(Val::Px(15.0)),
..default() ..scroll_view_node()
}, },
BackgroundColor(CLR_2), BG_COLOR_2,
ScrollView::default(), ScrollView::default(),
)) ))
.with_children(|p| { .with_children(|p| {
p.spawn(( p.spawn(ScrollableContent::default())
Node { .with_children(|scroll_area| {
flex_direction: bevy::ui::FlexDirection::Column, for i in 1..21 {
width: Val::Percent(100.0), scroll_area
..default() .spawn((base_node.clone(), BorderColor(CLR_3)))
}, .with_child((Text::new(format!("Nr {} out of 20", i)), TEXT_COLOR));
ScrollableContent::default(), }
)) });
.with_children(|scroll_area| {
for i in 0..21 {
scroll_area
.spawn((
Node {
min_width: Val::Px(200.0),
margin: UiRect::all(Val::Px(15.0)),
border: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(30.0)),
..default()
},
BorderColor(CLR_3),
))
.with_children(|p| {
p.spawn((
Text::new(format!("Nr {}", i)),
TextFont {
font_size: 25.0,
..default()
},
TextColor(CLR_3),
TextLayout::new_with_justify(JustifyText::Center),
));
});
}
});
}); });
}); });
} }
#[derive(Component, PartialEq, Debug, Clone, Copy)] fn scroll_to_top(_t: Trigger<Pointer<Released>>, mut scroll: Single<&mut ScrollableContent>) {
#[require(Button)] scroll.scroll_to_top();
enum ScrollButton {
MoveToTop,
MoveToBottom,
} }
fn reset_scroll( fn scroll_to_bottom(_t: Trigger<Pointer<Released>>, mut scroll: Single<&mut ScrollableContent>) {
q: Query<(&Interaction, &ScrollButton), Changed<Interaction>>, scroll.scroll_to_bottom();
mut scrolls_q: Query<&mut ScrollableContent>,
) {
let Ok(mut scroll) = scrolls_q.get_single_mut() else {
return;
};
for (interaction, action) in q.iter() {
if interaction != &Interaction::Pressed {
continue;
}
match action {
ScrollButton::MoveToTop => scroll.scroll_to_top(),
ScrollButton::MoveToBottom => scroll.scroll_to_bottom(),
}
}
} }

View File

@ -39,7 +39,7 @@ impl Plugin for ScrollViewPlugin {
/// Root component of scroll, it should have clipped style. /// Root component of scroll, it should have clipped style.
#[derive(Component, Debug, Reflect)] #[derive(Component, Debug, Reflect)]
#[require(Interaction, Node)] #[require(Interaction, Node = scroll_view_node())]
pub struct ScrollView { pub struct ScrollView {
/// Field which control speed of the scrolling. /// Field which control speed of the scrolling.
/// Could be negative number to implement invert scroll /// Could be negative number to implement invert scroll
@ -57,13 +57,40 @@ impl Default for ScrollView {
/// Component containing offset value of the scroll container to the parent. /// Component containing offset value of the scroll container to the parent.
/// It is possible to update the field `pos_y` manually to move scrollview to desired location. /// It is possible to update the field `pos_y` manually to move scrollview to desired location.
#[derive(Component, Debug, Reflect, Default)] #[derive(Component, Debug, Reflect, Default)]
#[require(Node(scroll_view_node))] #[require(Node = scroll_content_node())]
pub struct ScrollableContent { pub struct ScrollableContent {
/// Scroll container offset to the `ScrollView`. /// Scroll container offset to the `ScrollView`.
pub pos_y: f32, pub pos_y: f32,
/// Maximum value for the scroll. It is updated automatically based on the size of the children nodes.
pub max_scroll: f32, pub max_scroll: f32,
} }
impl ScrollableContent {
/// Scrolls to the top of the scroll view.
pub fn scroll_to_top(&mut self) {
self.pos_y = 0.0;
}
/// Scrolls to the bottom of the scroll view.
pub fn scroll_to_bottom(&mut self) {
self.pos_y = -self.max_scroll;
}
/// Scrolls by a specified amount.
///
/// # Parameters
/// - `value`: The amount to scroll vertically. Positive values scroll down,
/// and negative values scroll up.
///
/// Ensures the new position is clamped between the valid scroll range.
pub fn scroll_by(&mut self, value: f32) {
self.pos_y += value;
self.pos_y = self.pos_y.clamp(-self.max_scroll, 0.);
}
}
/// Creates a default scroll view node.
///
/// This function defines the visual and layout properties of a scrollable container.
pub fn scroll_view_node() -> Node { pub fn scroll_view_node() -> Node {
Node { Node {
overflow: Overflow::clip(), overflow: Overflow::clip(),
@ -74,19 +101,19 @@ pub fn scroll_view_node() -> Node {
} }
} }
impl ScrollableContent { /// Creates a default scroll content node.
pub fn scroll_to_top(&mut self) { pub fn scroll_content_node() -> Node {
self.pos_y = 0.0; Node {
} flex_direction: bevy::ui::FlexDirection::Column,
pub fn scroll_to_bottom(&mut self) { width: Val::Percent(100.0),
self.pos_y = -self.max_scroll; ..default()
}
pub fn scroll_by(&mut self, value: f32) {
self.pos_y += value;
self.pos_y = self.pos_y.clamp(-self.max_scroll, 0.);
} }
} }
/// Applies the default scroll view style to newly added `ScrollView` components.
///
/// This function updates the style of all new `ScrollView` nodes with the default
/// properties defined in `scroll_view_node`.
pub fn create_scroll_view(mut q: Query<&mut Node, Added<ScrollView>>) { pub fn create_scroll_view(mut q: Query<&mut Node, Added<ScrollView>>) {
let Node { let Node {
overflow, overflow,
@ -113,7 +140,7 @@ fn input_mouse_pressed_move(
if interaction != Interaction::Pressed { if interaction != Interaction::Pressed {
continue; continue;
} }
for &child in children.iter() { for child in children.iter() {
let Ok(mut scroll) = content_q.get_mut(child) else { let Ok(mut scroll) = content_q.get_mut(child) else {
continue; continue;
}; };
@ -129,7 +156,7 @@ fn update_size(
) { ) {
for (children, scroll_view_node) in q.iter_mut() { for (children, scroll_view_node) in q.iter_mut() {
let container_height = scroll_view_node.size().y * scroll_view_node.inverse_scale_factor(); let container_height = scroll_view_node.size().y * scroll_view_node.inverse_scale_factor();
for &child in children.iter() { for child in children.iter() {
let Ok((mut scroll, node)) = content_q.get_mut(child) else { let Ok((mut scroll, node)) = content_q.get_mut(child) else {
continue; continue;
}; };
@ -159,7 +186,7 @@ fn input_touch_pressed_move(
if interaction != Interaction::Pressed { if interaction != Interaction::Pressed {
continue; continue;
} }
for &child in children.iter() { for child in children.iter() {
let Ok(mut scroll) = content_q.get_mut(child) else { let Ok(mut scroll) = content_q.get_mut(child) else {
continue; continue;
}; };
@ -190,7 +217,7 @@ fn scroll_events(
#[cfg(feature = "extra_logs")] #[cfg(feature = "extra_logs")]
info!("Scroolling by {:#?}: {} movement", ev.unit, y); info!("Scroolling by {:#?}: {} movement", ev.unit, y);
for &child in children.iter() { for child in children.iter() {
let Ok(mut scroll) = content_q.get_mut(child) else { let Ok(mut scroll) = content_q.get_mut(child) else {
continue; continue;
}; };