Compare commits
10 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
17d4cfb263 | |
|
|
570d00d99a | |
|
|
bf2f27231a | |
|
|
74245f1e20 | |
|
|
2c9e8f45ba | |
|
|
c9f0844c02 | |
|
|
175914f65c | |
|
|
6d1fb83636 | |
|
|
2f23d23b02 | |
|
|
97201b99cc |
|
|
@ -0,0 +1,158 @@
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Pass the version
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release_info:
|
||||||
|
description: Information about release
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
dry_run:
|
||||||
|
description: Perform test without releasing
|
||||||
|
type: choice
|
||||||
|
required: true
|
||||||
|
default: "true"
|
||||||
|
options:
|
||||||
|
- "true"
|
||||||
|
- "false"
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
name: Prepare job settings
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.setup.outputs.version }}
|
||||||
|
dry_run: ${{ steps.setup.outputs.dry_run }}
|
||||||
|
info: ${{ steps.setup.outputs.info }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
- name: Get the release version from the tag and info from commit
|
||||||
|
id: version_push
|
||||||
|
shell: bash
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
run: |
|
||||||
|
echo version=${GITHUB_REF#refs/tags/} >> $GITHUB_OUTPUT
|
||||||
|
echo info=$(git tag -l --format='%(contents)' ${GITHUB_REF#refs/tags/}) >> $GITHUB_OUTPUT
|
||||||
|
- name: Get the release version from the input
|
||||||
|
id: version_dispatch
|
||||||
|
shell: bash
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
run: |
|
||||||
|
echo
|
||||||
|
echo version=$(echo ${{ inputs.version }} | xargs) >> $GITHUB_OUTPUT
|
||||||
|
echo dry_run=$(echo ${{ inputs.dry_run }} | xargs) >> $GITHUB_OUTPUT
|
||||||
|
echo info="${{ inputs.release_info }}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Setup
|
||||||
|
id: setup
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo version=$(if [ -n "${{ steps.version_dispatch.outputs.version }}" ]; then echo "${{ steps.version_dispatch.outputs.version }}"; else echo "${{ steps.version_push.outputs.version }}"; fi) >> $GITHUB_OUTPUT
|
||||||
|
echo dry_run=$(if [ -n "${{ steps.version_dispatch.outputs.dry_run }}" ]; then echo "${{ steps.version_dispatch.outputs.dry_run }}"; else echo "false"; fi) >> $GITHUB_OUTPUT
|
||||||
|
echo info=$(if [ -n "${{ steps.version_dispatch.outputs.info }}" ]; then echo "${{ steps.version_dispatch.outputs.info }}"; else echo "${{ steps.version_push.outputs.info }}"; fi) >> $GITHUB_OUTPUT
|
||||||
|
- name: Display settings
|
||||||
|
shell: bash
|
||||||
|
run: echo "Version ${{ steps.setup.outputs.version }}, Dry run- ${{ steps.setup.outputs.dry_run }}, info- ${{ steps.setup.outputs.info }}"
|
||||||
|
- name: Validate input
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -z "${{ steps.setup.outputs.version }}" ]; then exit 1; fi;
|
||||||
|
if [ -z "${{ steps.setup.outputs.dry_run }}" ]; then exit 1; fi;
|
||||||
|
if [ -z "${{ steps.setup.outputs.info }}" ]; then exit 1; fi;
|
||||||
|
if [[ "${{ steps.setup.outputs.version }}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+).*?$ ]]; then echo "Valid version"; else echo "INVALID VERSION FORMAT!";exit 1; fi;
|
||||||
|
build-and-upload:
|
||||||
|
name: Build and upload
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- build: linux
|
||||||
|
os: ubuntu-latest
|
||||||
|
target: x86_64-unknown-linux-musl
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: ${{ github.ref || github.run_id }}
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: true
|
||||||
|
command: build
|
||||||
|
args: --verbose --release --target ${{ matrix.target }}
|
||||||
|
- name: Extract changelog content
|
||||||
|
id: extract_changelog
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
version="${{ needs.setup.outputs.version }}"
|
||||||
|
echo "${{ needs.setup.outputs.info }}" > changelog_output.txt
|
||||||
|
awk "/^## \\[$version\\]/ {flag=1; next} /^## \\[/ && flag {flag=0} flag" CHANGELOG.md >> changelog_output.txt
|
||||||
|
- name: Display extracted content
|
||||||
|
run: cat changelog_output.txt
|
||||||
|
- name: Release
|
||||||
|
if: ${{ needs.setup.outputs.dry_run == 'false'}}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
body_path: changelog_output.txt
|
||||||
|
deploy-to-crates-io:
|
||||||
|
needs:
|
||||||
|
- setup
|
||||||
|
- build-and-upload
|
||||||
|
name: Deploy to crates.io
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/bin/
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: ${{ github.ref || github.run_id }}
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
- uses: taiki-e/cache-cargo-install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-release
|
||||||
|
- name: cargo publish dry run
|
||||||
|
if: ${{ needs.setup.outputs.dry_run == 'true'}}
|
||||||
|
run: cargo publish --dry-run
|
||||||
|
- name: cargo login
|
||||||
|
run: cargo login ${{ secrets.CRATES_IO_TOKEN }}
|
||||||
|
- name: "cargo release publish"
|
||||||
|
if: ${{ needs.setup.outputs.dry_run == 'false'}}
|
||||||
|
run: |-
|
||||||
|
cargo release \
|
||||||
|
publish \
|
||||||
|
--workspace \
|
||||||
|
--all-features \
|
||||||
|
--allow-branch HEAD \
|
||||||
|
--no-confirm \
|
||||||
|
--no-verify \
|
||||||
|
--execute
|
||||||
39
CHANGELOG.md
39
CHANGELOG.md
|
|
@ -1,5 +1,44 @@
|
||||||
# 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]
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Size calculation missmatch [#3](https://github.com/Leinnan/bevy_simple_scroll_view/issues/3)
|
||||||
|
|
||||||
|
## [0.3.0]
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- scroll to bottom and top functions for `ScrollableContent` component.
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Updated to Bevy 0.15
|
||||||
|
|
||||||
|
## [0.2.0]
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
|
||||||
|
- Updated to Bevy 0.14
|
||||||
|
|
||||||
## [0.1.0]
|
## [0.1.0]
|
||||||
|
|
||||||
- Initial version
|
- Initial version
|
||||||
|
|
|
||||||
13
Cargo.toml
13
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "bevy_simple_scroll_view"
|
name = "bevy_simple_scroll_view"
|
||||||
version = "0.1.0"
|
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,10 +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.13"
|
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.13"
|
version = "0.16"
|
||||||
default-features = true
|
default-features = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
extra_logs = ["bevy/bevy_log"]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
missing_docs = "warn"
|
||||||
|
|
|
||||||
|
|
@ -36,4 +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.14 | 0.2
|
||||||
0.13 | 0.1
|
0.13 | 0.1
|
||||||
|
|
|
||||||
|
|
@ -1,122 +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::rgb(0.168, 0.168, 0.168);
|
const BG_COLOR: BackgroundColor = BackgroundColor(Color::srgb(0.168, 0.168, 0.168));
|
||||||
const CLR_2: Color = Color::rgb(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::rgb(0.569, 0.592, 0.647);
|
const CLR_3: Color = Color::srgb(0.569, 0.592, 0.647);
|
||||||
const CLR_4: Color = Color::rgb(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) {
|
||||||
commands.spawn(Camera2dBundle::default());
|
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
|
commands
|
||||||
.spawn(NodeBundle {
|
.spawn((
|
||||||
style: Style {
|
BG_COLOR,
|
||||||
|
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()
|
||||||
},
|
},
|
||||||
background_color: CLR_1.into(),
|
))
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|p| {
|
.with_children(|p| {
|
||||||
p.spawn(ButtonBundle {
|
p.spawn(Node {
|
||||||
style: Style {
|
width: Val::Percent(20.0),
|
||||||
margin: UiRect::all(Val::Px(15.0)),
|
margin: UiRect::all(Val::Px(15.0)),
|
||||||
padding: UiRect::all(Val::Px(15.0)),
|
flex_direction: FlexDirection::Column,
|
||||||
max_height: Val::Px(100.0),
|
|
||||||
border: UiRect::all(Val::Px(3.0)),
|
|
||||||
align_items: AlignItems::Center,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
background_color: CLR_2.into(),
|
|
||||||
border_color: CLR_4.into(),
|
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.with_children(|p| {
|
.with_children(|p| {
|
||||||
p.spawn(TextBundle::from_section(
|
p.spawn((Text::new("Scroll to:"), TEXT_COLOR));
|
||||||
"Reset scroll",
|
let btn = (base_node.clone(), BG_COLOR_2, Button);
|
||||||
TextStyle {
|
p.spawn(btn.clone())
|
||||||
font_size: 25.0,
|
.observe(scroll_to_top)
|
||||||
color: CLR_4,
|
.with_child((Text::new("top"), TEXT_COLOR));
|
||||||
..default()
|
p.spawn(btn)
|
||||||
},
|
.observe(scroll_to_bottom)
|
||||||
));
|
.with_child((Text::new("bottom"), TEXT_COLOR));
|
||||||
});
|
});
|
||||||
p.spawn((
|
p.spawn((
|
||||||
NodeBundle {
|
Node {
|
||||||
style: Style {
|
|
||||||
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()
|
||||||
},
|
|
||||||
background_color: CLR_2.into(),
|
|
||||||
..default()
|
|
||||||
},
|
},
|
||||||
|
BG_COLOR_2,
|
||||||
ScrollView::default(),
|
ScrollView::default(),
|
||||||
))
|
))
|
||||||
.with_children(|p| {
|
.with_children(|p| {
|
||||||
p.spawn((
|
p.spawn(ScrollableContent::default())
|
||||||
NodeBundle {
|
|
||||||
style: Style {
|
|
||||||
flex_direction: bevy::ui::FlexDirection::Column,
|
|
||||||
width: Val::Percent(100.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
ScrollableContent::default(),
|
|
||||||
))
|
|
||||||
.with_children(|scroll_area| {
|
.with_children(|scroll_area| {
|
||||||
for i in 0..21 {
|
for i in 1..21 {
|
||||||
scroll_area
|
scroll_area
|
||||||
.spawn(NodeBundle {
|
.spawn((base_node.clone(), BorderColor(CLR_3)))
|
||||||
style: Style {
|
.with_child((Text::new(format!("Nr {} out of 20", i)), TEXT_COLOR));
|
||||||
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()
|
|
||||||
},
|
|
||||||
border_color: CLR_3.into(),
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
.with_children(|p| {
|
|
||||||
p.spawn(
|
|
||||||
TextBundle::from_section(
|
|
||||||
format!("Nr {}", i),
|
|
||||||
TextStyle {
|
|
||||||
font_size: 25.0,
|
|
||||||
color: CLR_3,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.with_text_justify(JustifyText::Center),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_scroll(
|
fn scroll_to_top(_t: Trigger<Pointer<Released>>, mut scroll: Single<&mut ScrollableContent>) {
|
||||||
q: Query<(&Button, &Interaction), Changed<Interaction>>,
|
scroll.scroll_to_top();
|
||||||
mut scrolls_q: Query<&mut ScrollableContent>,
|
|
||||||
) {
|
|
||||||
for (_, interaction) in q.iter() {
|
|
||||||
if interaction == &Interaction::Pressed {
|
|
||||||
for mut scroll in scrolls_q.iter_mut() {
|
|
||||||
scroll.pos_y = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_to_bottom(_t: Trigger<Pointer<Released>>, mut scroll: Single<&mut ScrollableContent>) {
|
||||||
|
scroll.scroll_to_bottom();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
record.gif
BIN
record.gif
Binary file not shown.
|
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 182 KiB |
169
src/lib.rs
169
src/lib.rs
|
|
@ -8,7 +8,7 @@ use bevy::{
|
||||||
/// A `Plugin` providing the systems and components required to make a ScrollView work.
|
/// A `Plugin` providing the systems and components required to make a ScrollView work.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// use bevy::prelude::*;
|
/// use bevy::prelude::*;
|
||||||
/// use bevy_simple_scroll_view::*;
|
/// use bevy_simple_scroll_view::*;
|
||||||
///
|
///
|
||||||
|
|
@ -26,6 +26,7 @@ impl Plugin for ScrollViewPlugin {
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
create_scroll_view,
|
create_scroll_view,
|
||||||
|
update_size,
|
||||||
input_mouse_pressed_move,
|
input_mouse_pressed_move,
|
||||||
input_touch_pressed_move,
|
input_touch_pressed_move,
|
||||||
scroll_events,
|
scroll_events,
|
||||||
|
|
@ -38,6 +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 = 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
|
||||||
|
|
@ -47,7 +49,7 @@ pub struct ScrollView {
|
||||||
impl Default for ScrollView {
|
impl Default for ScrollView {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scroll_speed: 200.0,
|
scroll_speed: 1200.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,69 +57,140 @@ 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_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 fn create_scroll_view(
|
impl ScrollableContent {
|
||||||
mut commands: Commands,
|
/// Scrolls to the top of the scroll view.
|
||||||
mut q: Query<(Entity, &mut Style), Added<ScrollView>>,
|
pub fn scroll_to_top(&mut self) {
|
||||||
) {
|
self.pos_y = 0.0;
|
||||||
for (e, mut style) in q.iter_mut() {
|
}
|
||||||
style.overflow = Overflow::clip();
|
/// Scrolls to the bottom of the scroll view.
|
||||||
style.align_items = AlignItems::Start;
|
pub fn scroll_to_bottom(&mut self) {
|
||||||
style.align_self = AlignSelf::Stretch;
|
self.pos_y = -self.max_scroll;
|
||||||
style.flex_direction = FlexDirection::Row;
|
}
|
||||||
commands.entity(e).insert(Interaction::None);
|
|
||||||
|
/// 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(),
|
||||||
|
align_items: AlignItems::Start,
|
||||||
|
align_self: AlignSelf::Stretch,
|
||||||
|
flex_direction: FlexDirection::Row,
|
||||||
|
..default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
align_items,
|
||||||
|
align_self,
|
||||||
|
flex_direction,
|
||||||
|
..
|
||||||
|
} = scroll_view_node();
|
||||||
|
for mut style in q.iter_mut() {
|
||||||
|
style.overflow = overflow;
|
||||||
|
style.align_items = align_items;
|
||||||
|
style.align_self = align_self;
|
||||||
|
style.flex_direction = flex_direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_mouse_pressed_move(
|
fn input_mouse_pressed_move(
|
||||||
mut motion_evr: EventReader<MouseMotion>,
|
mut motion_evr: EventReader<MouseMotion>,
|
||||||
mut q: Query<(&Children, &Interaction, &Node), With<ScrollView>>,
|
mut q: Query<(&Children, &Interaction), With<ScrollView>>,
|
||||||
mut content_q: Query<(&mut ScrollableContent, &Node)>,
|
mut content_q: Query<&mut ScrollableContent>,
|
||||||
) {
|
) {
|
||||||
for evt in motion_evr.read() {
|
for evt in motion_evr.read() {
|
||||||
for (children, &interaction, node) in q.iter_mut() {
|
for (children, &interaction) in q.iter_mut() {
|
||||||
if interaction != Interaction::Pressed {
|
if interaction != Interaction::Pressed {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let container_height = node.size().y;
|
for child in children.iter() {
|
||||||
for &child in children.iter() {
|
let Ok(mut scroll) = content_q.get_mut(child) else {
|
||||||
if let Ok(item) = content_q.get_mut(child) {
|
continue;
|
||||||
let mut scroll = item.0;
|
};
|
||||||
let max_scroll = (item.1.size().y - container_height).max(0.0);
|
scroll.scroll_by(evt.delta.y);
|
||||||
scroll.pos_y += evt.delta.y;
|
|
||||||
scroll.pos_y = scroll.pos_y.clamp(-max_scroll, 0.);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_size(
|
||||||
|
mut q: Query<(&Children, &ComputedNode), With<ScrollView>>,
|
||||||
|
mut content_q: Query<(&mut ScrollableContent, &ComputedNode), Changed<ComputedNode>>,
|
||||||
|
) {
|
||||||
|
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 * node.inverse_scale_factor() - container_height).max(0.0);
|
||||||
|
#[cfg(feature = "extra_logs")]
|
||||||
|
info!(
|
||||||
|
"CONTAINER {}, max_scroll: {}",
|
||||||
|
container_height, scroll.max_scroll
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_touch_pressed_move(
|
fn input_touch_pressed_move(
|
||||||
touches: Res<Touches>,
|
touches: Res<Touches>,
|
||||||
mut q: Query<(&Children, &Interaction, &Node), With<ScrollView>>,
|
mut q: Query<(&Children, &Interaction), With<ScrollView>>,
|
||||||
mut content_q: Query<(&mut ScrollableContent, &Node)>,
|
mut content_q: Query<&mut ScrollableContent>,
|
||||||
) {
|
) {
|
||||||
for t in touches.iter() {
|
for t in touches.iter() {
|
||||||
let Some(touch) = touches.get_pressed(t.id()) else {
|
let Some(touch) = touches.get_pressed(t.id()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (children, &interaction, node) in q.iter_mut() {
|
for (children, &interaction) in q.iter_mut() {
|
||||||
if interaction != Interaction::Pressed {
|
if interaction != Interaction::Pressed {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let container_height = node.size().y;
|
for child in children.iter() {
|
||||||
for &child in children.iter() {
|
let Ok(mut scroll) = content_q.get_mut(child) else {
|
||||||
if let Ok(item) = content_q.get_mut(child) {
|
continue;
|
||||||
let mut scroll = item.0;
|
};
|
||||||
let max_scroll = (item.1.size().y - container_height).max(0.0);
|
scroll.scroll_by(touch.delta().y);
|
||||||
scroll.pos_y += touch.delta().y;
|
|
||||||
scroll.pos_y = scroll.pos_y.clamp(-max_scroll, 0.);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,38 +198,36 @@ fn input_touch_pressed_move(
|
||||||
|
|
||||||
fn scroll_events(
|
fn scroll_events(
|
||||||
mut scroll_evr: EventReader<MouseWheel>,
|
mut scroll_evr: EventReader<MouseWheel>,
|
||||||
mut q: Query<(&Children, &Interaction, &ScrollView, &Node), With<ScrollView>>,
|
mut q: Query<(&Children, &Interaction, &ScrollView), With<ScrollView>>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut content_q: Query<(&mut ScrollableContent, &Node)>,
|
mut content_q: Query<&mut ScrollableContent>,
|
||||||
) {
|
) {
|
||||||
use bevy::input::mouse::MouseScrollUnit;
|
use bevy::input::mouse::MouseScrollUnit;
|
||||||
for ev in scroll_evr.read() {
|
for ev in scroll_evr.read() {
|
||||||
for (children, &interaction, scroll_view, node) in q.iter_mut() {
|
for (children, &interaction, scroll_view) in q.iter_mut() {
|
||||||
|
if interaction != Interaction::Hovered {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let y = match ev.unit {
|
let y = match ev.unit {
|
||||||
MouseScrollUnit::Line => {
|
MouseScrollUnit::Line => {
|
||||||
ev.y * time.delta().as_secs_f32() * scroll_view.scroll_speed
|
ev.y * time.delta().as_secs_f32() * scroll_view.scroll_speed
|
||||||
}
|
}
|
||||||
MouseScrollUnit::Pixel => ev.y,
|
MouseScrollUnit::Pixel => ev.y,
|
||||||
};
|
};
|
||||||
if interaction != Interaction::Hovered {
|
#[cfg(feature = "extra_logs")]
|
||||||
|
info!("Scroolling by {:#?}: {} movement", ev.unit, y);
|
||||||
|
|
||||||
|
for child in children.iter() {
|
||||||
|
let Ok(mut scroll) = content_q.get_mut(child) else {
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
let container_height = node.size().y;
|
scroll.scroll_by(y);
|
||||||
|
|
||||||
for &child in children.iter() {
|
|
||||||
if let Ok(item) = content_q.get_mut(child) {
|
|
||||||
let y = y * time.delta().as_secs_f32() * scroll_view.scroll_speed;
|
|
||||||
let mut scroll = item.0;
|
|
||||||
let max_scroll = (item.1.size().y - container_height).max(0.0);
|
|
||||||
scroll.pos_y += y;
|
|
||||||
scroll.pos_y = scroll.pos_y.clamp(-max_scroll, 0.);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_update(mut q: Query<(&ScrollableContent, &mut Style), Changed<ScrollableContent>>) {
|
fn scroll_update(mut q: Query<(&ScrollableContent, &mut Node), Changed<ScrollableContent>>) {
|
||||||
for (scroll, mut style) in q.iter_mut() {
|
for (scroll, mut style) in q.iter_mut() {
|
||||||
style.top = Val::Px(scroll.pos_y);
|
style.top = Val::Px(scroll.pos_y);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue