Compare commits
7 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
17d4cfb263 | |
|
|
570d00d99a | |
|
|
bf2f27231a | |
|
|
74245f1e20 | |
|
|
2c9e8f45ba | |
|
|
c9f0844c02 | |
|
|
175914f65c |
23
CHANGELOG.md
23
CHANGELOG.md
|
|
@ -1,5 +1,28 @@
|
|||
# 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]
|
||||
|
||||
## Fixed
|
||||
|
||||
- Size calculation missmatch [#3](https://github.com/Leinnan/bevy_simple_scroll_view/issues/3)
|
||||
|
||||
## [0.3.0]
|
||||
|
||||
## Added
|
||||
|
|
|
|||
15
Cargo.toml
15
Cargo.toml
|
|
@ -1,24 +1,27 @@
|
|||
[package]
|
||||
name = "bevy_simple_scroll_view"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
exclude = [".github/","wasm/", "record.gif"]
|
||||
exclude = [".github/", "wasm/", "record.gif"]
|
||||
categories = ["game-development", "gui"]
|
||||
keywords = ["bevy","ui"]
|
||||
keywords = ["bevy", "ui"]
|
||||
repository = "https://github.com/Leinnan/bevy_simple_scroll_view"
|
||||
homepage = "https://github.com/Leinnan/bevy_simple_scroll_view"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Simple to use plugin implementing ScrollView into Bevy engine."
|
||||
|
||||
[dependencies.bevy]
|
||||
version = "0.15"
|
||||
version = "0.16"
|
||||
default-features = false
|
||||
features = ["bevy_ui", "bevy_asset", "bevy_text"]
|
||||
|
||||
[dev-dependencies.bevy]
|
||||
version = "0.15"
|
||||
version = "0.16"
|
||||
default-features = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
extra_logs = []
|
||||
extra_logs = ["bevy/bevy_log"]
|
||||
|
||||
[lints.rust]
|
||||
missing_docs = "warn"
|
||||
|
|
|
|||
|
|
@ -36,5 +36,7 @@ Please keep PRs small and scoped to a single feature or fix.
|
|||
|
||||
Bevy version | crate version
|
||||
--- | ---
|
||||
0.16 | 0.4
|
||||
0.15 | 0.3
|
||||
0.14 | 0.2
|
||||
0.13 | 0.1
|
||||
|
|
|
|||
|
|
@ -1,135 +1,84 @@
|
|||
#![allow(missing_docs)]
|
||||
|
||||
use bevy::picking::events::{Pointer, Released};
|
||||
use bevy::prelude::*;
|
||||
use bevy_simple_scroll_view::*;
|
||||
|
||||
const CLR_1: Color = Color::srgb(0.168, 0.168, 0.168);
|
||||
const CLR_2: Color = Color::srgb(0.109, 0.109, 0.109);
|
||||
const BG_COLOR: BackgroundColor = BackgroundColor(Color::srgb(0.168, 0.168, 0.168));
|
||||
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_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() {
|
||||
App::new()
|
||||
.add_plugins((DefaultPlugins, ScrollViewPlugin))
|
||||
.add_systems(Startup, prepare)
|
||||
.add_systems(Update, reset_scroll)
|
||||
.add_systems(Startup, setup)
|
||||
.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((
|
||||
BackgroundColor(CLR_1),
|
||||
BG_COLOR,
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
padding: UiRect::all(Val::Px(15.0)),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|p| {
|
||||
p.spawn(Node {
|
||||
width: Val::Percent(20.0),
|
||||
margin: UiRect::all(Val::Px(10.0)),
|
||||
margin: UiRect::all(Val::Px(15.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
..default()
|
||||
})
|
||||
.with_children(|p| {
|
||||
for btn_action in [ScrollButton::MoveToTop, ScrollButton::MoveToBottom] {
|
||||
p.spawn((
|
||||
Node {
|
||||
margin: UiRect::all(Val::Px(15.0)),
|
||||
padding: UiRect::all(Val::Px(15.0)),
|
||||
max_height: Val::Px(100.0),
|
||||
border: UiRect::all(Val::Px(3.0)),
|
||||
align_items: AlignItems::Center,
|
||||
..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((Text::new("Scroll to:"), TEXT_COLOR));
|
||||
let btn = (base_node.clone(), BG_COLOR_2, Button);
|
||||
p.spawn(btn.clone())
|
||||
.observe(scroll_to_top)
|
||||
.with_child((Text::new("top"), TEXT_COLOR));
|
||||
p.spawn(btn)
|
||||
.observe(scroll_to_bottom)
|
||||
.with_child((Text::new("bottom"), TEXT_COLOR));
|
||||
});
|
||||
p.spawn((
|
||||
Node {
|
||||
width: Val::Percent(80.0),
|
||||
margin: UiRect::all(Val::Px(15.0)),
|
||||
..default()
|
||||
..scroll_view_node()
|
||||
},
|
||||
BackgroundColor(CLR_2),
|
||||
BG_COLOR_2,
|
||||
ScrollView::default(),
|
||||
))
|
||||
.with_children(|p| {
|
||||
p.spawn((
|
||||
Node {
|
||||
flex_direction: bevy::ui::FlexDirection::Column,
|
||||
width: Val::Percent(100.0),
|
||||
..default()
|
||||
},
|
||||
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),
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
p.spawn(ScrollableContent::default())
|
||||
.with_children(|scroll_area| {
|
||||
for i in 1..21 {
|
||||
scroll_area
|
||||
.spawn((base_node.clone(), BorderColor(CLR_3)))
|
||||
.with_child((Text::new(format!("Nr {} out of 20", i)), TEXT_COLOR));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component, PartialEq, Debug, Clone, Copy)]
|
||||
#[require(Button)]
|
||||
enum ScrollButton {
|
||||
MoveToTop,
|
||||
MoveToBottom,
|
||||
fn scroll_to_top(_t: Trigger<Pointer<Released>>, mut scroll: Single<&mut ScrollableContent>) {
|
||||
scroll.scroll_to_top();
|
||||
}
|
||||
|
||||
fn reset_scroll(
|
||||
q: Query<(&Interaction, &ScrollButton), Changed<Interaction>>,
|
||||
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(),
|
||||
}
|
||||
}
|
||||
fn scroll_to_bottom(_t: Trigger<Pointer<Released>>, mut scroll: Single<&mut ScrollableContent>) {
|
||||
scroll.scroll_to_bottom();
|
||||
}
|
||||
|
|
|
|||
66
src/lib.rs
66
src/lib.rs
|
|
@ -39,7 +39,7 @@ impl Plugin for ScrollViewPlugin {
|
|||
|
||||
/// Root component of scroll, it should have clipped style.
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
#[require(Interaction, Node)]
|
||||
#[require(Interaction, Node = scroll_view_node())]
|
||||
pub struct ScrollView {
|
||||
/// Field which control speed of the scrolling.
|
||||
/// 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.
|
||||
/// It is possible to update the field `pos_y` manually to move scrollview to desired location.
|
||||
#[derive(Component, Debug, Reflect, Default)]
|
||||
#[require(Node(scroll_view_node))]
|
||||
#[require(Node = scroll_content_node())]
|
||||
pub struct ScrollableContent {
|
||||
/// Scroll container offset to the `ScrollView`.
|
||||
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,
|
||||
}
|
||||
|
||||
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 {
|
||||
Node {
|
||||
overflow: Overflow::clip(),
|
||||
|
|
@ -74,19 +101,19 @@ pub fn scroll_view_node() -> Node {
|
|||
}
|
||||
}
|
||||
|
||||
impl ScrollableContent {
|
||||
pub fn scroll_to_top(&mut self) {
|
||||
self.pos_y = 0.0;
|
||||
}
|
||||
pub fn scroll_to_bottom(&mut self) {
|
||||
self.pos_y = -self.max_scroll;
|
||||
}
|
||||
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 content node.
|
||||
pub fn scroll_content_node() -> Node {
|
||||
Node {
|
||||
flex_direction: bevy::ui::FlexDirection::Column,
|
||||
width: Val::Percent(100.0),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>>) {
|
||||
let Node {
|
||||
overflow,
|
||||
|
|
@ -113,7 +140,7 @@ fn input_mouse_pressed_move(
|
|||
if interaction != Interaction::Pressed {
|
||||
continue;
|
||||
}
|
||||
for &child in children.iter() {
|
||||
for child in children.iter() {
|
||||
let Ok(mut scroll) = content_q.get_mut(child) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -127,14 +154,15 @@ fn update_size(
|
|||
mut q: Query<(&Children, &ComputedNode), With<ScrollView>>,
|
||||
mut content_q: Query<(&mut ScrollableContent, &ComputedNode), Changed<ComputedNode>>,
|
||||
) {
|
||||
for (children, node) in q.iter_mut() {
|
||||
let container_height = node.size().y;
|
||||
for &child in children.iter() {
|
||||
for (children, scroll_view_node) in q.iter_mut() {
|
||||
let container_height = scroll_view_node.size().y * scroll_view_node.inverse_scale_factor();
|
||||
for child in children.iter() {
|
||||
let Ok((mut scroll, node)) = content_q.get_mut(child) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
scroll.max_scroll = (node.size().y - container_height).max(0.0);
|
||||
scroll.max_scroll =
|
||||
(node.size().y * node.inverse_scale_factor() - container_height).max(0.0);
|
||||
#[cfg(feature = "extra_logs")]
|
||||
info!(
|
||||
"CONTAINER {}, max_scroll: {}",
|
||||
|
|
@ -158,7 +186,7 @@ fn input_touch_pressed_move(
|
|||
if interaction != Interaction::Pressed {
|
||||
continue;
|
||||
}
|
||||
for &child in children.iter() {
|
||||
for child in children.iter() {
|
||||
let Ok(mut scroll) = content_q.get_mut(child) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -189,7 +217,7 @@ fn scroll_events(
|
|||
#[cfg(feature = "extra_logs")]
|
||||
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 {
|
||||
continue;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue