Skip to content

Commit

Permalink
port bevyengine#8104 to main
Browse files Browse the repository at this point in the history
  • Loading branch information
Piefayth committed Sep 16, 2024
1 parent 5a0c09d commit 640e7e1
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 50 deletions.
1 change: 1 addition & 0 deletions crates/bevy_ui/src/layout/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ impl From<OverflowAxis> 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,
}
}
}
Expand Down
17 changes: 13 additions & 4 deletions crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
BorderRadius, ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale,
BorderRadius, ContentSize, DefaultUiCamera, Node, Outline, ScrollPosition, Style, TargetCamera, UiScale
};
use bevy_ecs::{
change_detection::{DetectChanges, DetectChangesMut},
Expand Down Expand Up @@ -117,6 +117,7 @@ pub fn ui_layout_system(
&mut Transform,
Option<&BorderRadius>,
Option<&Outline>,
Option<&ScrollPosition>,
)>,
#[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut CosmicBuffer>,
#[cfg(feature = "bevy_text")] mut text_pipeline: ResMut<TextPipeline>,
Expand Down Expand Up @@ -276,6 +277,7 @@ pub fn ui_layout_system(
inverse_target_scale_factor,
Vec2::ZERO,
Vec2::ZERO,
Vec2::ZERO,
);
}

Expand All @@ -292,13 +294,15 @@ pub fn ui_layout_system(
&mut Transform,
Option<&BorderRadius>,
Option<&Outline>,
Option<&ScrollPosition>,
)>,
children_query: &Query<&Children>,
inverse_target_scale_factor: f32,
parent_size: Vec2,
parent_scroll_position: Vec2,
mut absolute_location: Vec2,
) {
if let Ok((mut node, mut transform, maybe_border_radius, maybe_outline)) =
if let Ok((mut node, mut transform, maybe_border_radius, maybe_outline, maybe_scroll_position)) =
node_transform_query.get_mut(entity)
{
let Ok(layout) = ui_surface.get_layout(entity) else {
Expand All @@ -310,12 +314,12 @@ pub fn ui_layout_system(
inverse_target_scale_factor * Vec2::new(layout.location.x, layout.location.y);

absolute_location += layout_location;

let rounded_size = approx_round_layout_coords(absolute_location + layout_size)
- approx_round_layout_coords(absolute_location);

let rounded_location =
approx_round_layout_coords(layout_location) + 0.5 * (rounded_size - parent_size);
approx_round_layout_coords(layout_location + parent_scroll_position) + 0.5 * (rounded_size - parent_size);

// only trigger change detection when the new values are different
if node.calculated_size != rounded_size || node.unrounded_size != layout_size {
Expand Down Expand Up @@ -351,6 +355,10 @@ pub fn ui_layout_system(
transform.translation = rounded_location.extend(0.);
}

let scroll_position: Vec2 = maybe_scroll_position
.map(|scroll_pos| scroll_pos.into())
.unwrap_or_else(Vec2::default);

if let Ok(children) = children_query.get(entity) {
for &child_uinode in children {
update_uinode_geometry_recursive(
Expand All @@ -361,6 +369,7 @@ pub fn ui_layout_system(
children_query,
inverse_target_scale_factor,
rounded_size,
scroll_position,
absolute_location,
);
}
Expand Down
11 changes: 9 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use bevy_transform::TransformSystem;
use layout::ui_surface::UiSurface;
use stack::ui_stack_system;
pub use stack::UiStack;
use update::{update_clipping_system, update_target_camera_system};
use update::{update_clipping_system, update_scroll_position, update_target_camera_system};

/// The basic plugin for Bevy UI
#[derive(Default)]
Expand All @@ -80,6 +80,10 @@ pub enum UiSystem {
///
/// Runs in [`PreUpdate`].
Focus,
/// After this label, scroll positions will have been updated for UI entities.
///
/// Runs in [`PreUpdate`].
Scroll,
/// All UI systems in [`PostUpdate`] will run in or after this label.
Prepare,
/// After this label, the ui layout state has been updated.
Expand Down Expand Up @@ -158,7 +162,10 @@ impl Plugin for UiPlugin {
)
.add_systems(
PreUpdate,
ui_focus_system.in_set(UiSystem::Focus).after(InputSystem),
(
ui_focus_system.in_set(UiSystem::Focus).after(InputSystem),
update_scroll_position.in_set(UiSystem::Scroll).after(UiSystem::Focus),
)
);

app.add_systems(
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
#[cfg(feature = "bevy_text")]
use crate::widget::TextFlags;
use crate::{
widget::{Button, UiImageSize},
BackgroundColor, BorderColor, BorderRadius, ContentSize, FocusPolicy, Interaction, Node, Style,
UiImage, UiMaterial, ZIndex,
widget::{Button, UiImageSize}, BackgroundColor, BorderColor, BorderRadius, ContentSize, FocusPolicy, Interaction, Node, ScrollPosition, Style, UiImage, UiMaterial, ZIndex
};
use bevy_asset::Handle;
#[cfg(feature = "bevy_text")]
Expand Down Expand Up @@ -38,6 +36,8 @@ pub struct NodeBundle {
pub border_radius: BorderRadius,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// The scroll position of the node,
pub scroll_position: ScrollPosition,
/// The transform of the node
///
/// This component is automatically managed by the UI layout system.
Expand Down
55 changes: 55 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,36 @@ impl Default for Node {
}
}


/// The scroll position on the node
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct ScrollPosition {
/// How far accross the node is scrolled (0 = not scrolled / scrolled to right)
pub offset_x: f32,
/// How far down the node is scrolled (0 = not scrolled / scrolled to top)
pub offset_y: f32,
}

impl ScrollPosition {
pub const DEFAULT: Self = Self {
offset_x: 0.0,
offset_y: 0.0,
};
}

impl Default for ScrollPosition {
fn default() -> Self {
Self::DEFAULT
}
}

impl From<&ScrollPosition> for Vec2 {
fn from(scroll_pos: &ScrollPosition) -> Self {
Vec2::new(scroll_pos.offset_x, scroll_pos.offset_y)
}
}

/// Describes the style of a UI container node
///
/// Nodes can be laid out using either Flexbox or CSS Grid Layout.
Expand Down Expand Up @@ -865,6 +895,29 @@ impl Overflow {
pub const fn is_visible(&self) -> bool {
self.x.is_visible() && self.y.is_visible()
}

pub const fn scroll() -> Self {
Self {
x: OverflowAxis::Scroll,
y: OverflowAxis::Scroll,
}
}

/// Scroll overflowing items on the x axis
pub const fn scroll_x() -> Self {
Self {
x: OverflowAxis::Scroll,
y: OverflowAxis::Visible,
}
}

/// Scroll overflowing items on the y axis
pub const fn scroll_y() -> Self {
Self {
x: OverflowAxis::Visible,
y: OverflowAxis::Scroll,
}
}
}

impl Default for Overflow {
Expand All @@ -888,6 +941,8 @@ pub enum OverflowAxis {
Clip,
/// Hide overflowing items by influencing layout and then clipping.
Hidden,
/// Scroll overflowing items.
Scroll,
}

impl OverflowAxis {
Expand Down
55 changes: 50 additions & 5 deletions crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//! This module contains systems that update the UI when something changes

use crate::{CalculatedClip, Display, OverflowAxis, Style, TargetCamera};
use crate::{CalculatedClip, Display, OverflowAxis, ScrollPosition, Style, TargetCamera};

use super::Node;
use bevy_ecs::{
entity::Entity,
query::{Changed, With, Without},
system::{Commands, Query},
entity::Entity, event::EventReader, query::{Changed, With, Without}, system::{Commands, Query, Res}
};
use bevy_hierarchy::{Children, Parent};
use bevy_math::Rect;
use bevy_input::mouse::{MouseScrollUnit, MouseWheel};
use bevy_math::{Rect, Vec2};
use bevy_picking::focus::HoverMap;
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashSet;

Expand Down Expand Up @@ -181,3 +181,48 @@ fn update_children_target_camera(
);
}
}

pub fn update_scroll_position(
mut mouse_wheel_events: EventReader<MouseWheel>,
hover_map: Res<HoverMap>,
mut scrolled_node_query: Query<(&mut ScrollPosition, &Style, &Children, &Node)>,
just_node_query: Query<&Node>,
) {
for mouse_wheel_event in mouse_wheel_events.read() {
// TODO: 90% sure this should be user-configurable, bevy shouldn't own scroll speed
let (dx, dy) = match mouse_wheel_event.unit {
MouseScrollUnit::Line => (mouse_wheel_event.x * 20., mouse_wheel_event.y * 20.),
MouseScrollUnit::Pixel => (mouse_wheel_event.x, mouse_wheel_event.y),
};

for (_pointer, pointer_map) in hover_map.iter() {
for (entity, _hit) in pointer_map.iter() {
if let Ok((mut scroll_position, style, children, scrolled_node)) = scrolled_node_query.get_mut(*entity) {
let Vec2 {
x: container_width,
y: container_height,
} = scrolled_node.size();

let (items_width, items_height): (f32, f32) =
children.iter().fold((0.0, 0.0), |sum, child| {
let size = just_node_query.get(*child).unwrap().size();
(sum.0 + size.x, sum.1 + size.y)
});

if style.overflow.x == OverflowAxis::Scroll {
let max_scroll_x = (items_width - container_width).max(0.);
scroll_position.offset_x =
(scroll_position.offset_x + dx).clamp(-max_scroll_x, 0.);
}
if style.overflow.y == OverflowAxis::Scroll {
let max_scroll_y = (items_height - container_height).max(0.);
scroll_position.offset_y =
(scroll_position.offset_y + dy).clamp(-max_scroll_y, 0.);
}

println!("new scroll possy: {:?} | scroll node size {:?} | items_w {:?} items_h {:?}", scroll_position, scrolled_node.size(), items_width, items_height);
}
}
}
}
}
44 changes: 8 additions & 36 deletions examples/ui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ fn main() {
app.add_plugins(DefaultPlugins)
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
.insert_resource(WinitSettings::desktop_app())
.add_systems(Startup, setup)
.add_systems(Update, mouse_scroll);
.add_systems(Startup, setup);

#[cfg(feature = "bevy_dev_tools")]
{
Expand All @@ -43,6 +42,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
})
.insert(Pickable::IGNORE)
.with_children(|parent| {
// left vertical fill (border)
parent
Expand Down Expand Up @@ -122,7 +122,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
width: Val::Px(200.),
..default()
},
background_color: Color::srgb(0.15, 0.15, 0.15).into(),
..default()
})
.with_children(|parent| {
Expand All @@ -138,14 +137,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
),
Label,
));
// List with hidden overflow
// Scrolling list
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
align_self: AlignSelf::Stretch,
height: Val::Percent(50.),
overflow: Overflow::clip_y(),
overflow: Overflow::scroll_y(),
..default()
},
background_color: Color::srgb(0.10, 0.10, 0.10).into(),
Expand All @@ -163,12 +162,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
},
ScrollingList::default(),
AccessibilityNode(NodeBuilder::new(Role::List)),
))
.insert(Pickable::IGNORE)
.with_children(|parent| {
// List items
for i in 0..30 {
for i in 0..100 {
parent.spawn((
TextBundle::from_section(
format!("Item {i}"),
Expand All @@ -185,6 +184,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
});
});

parent
.spawn(NodeBundle {
style: Style {
Expand Down Expand Up @@ -224,6 +224,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
..default()
})
.insert(Pickable::IGNORE)
.with_children(|parent| {
parent
.spawn(NodeBundle {
Expand Down Expand Up @@ -336,35 +337,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
}

#[derive(Component, Default)]
struct ScrollingList {
position: f32,
}

fn mouse_scroll(
mut mouse_wheel_events: EventReader<MouseWheel>,
mut query_list: Query<(&mut ScrollingList, &mut Style, &Parent, &Node)>,
query_node: Query<&Node>,
) {
for mouse_wheel_event in mouse_wheel_events.read() {
for (mut scrolling_list, mut style, parent, list_node) in &mut query_list {
let items_height = list_node.size().y;
let container_height = query_node.get(parent.get()).unwrap().size().y;

let max_scroll = (items_height - container_height).max(0.);

let dy = match mouse_wheel_event.unit {
MouseScrollUnit::Line => mouse_wheel_event.y * 20.,
MouseScrollUnit::Pixel => mouse_wheel_event.y,
};

scrolling_list.position += dy;
scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.);
style.top = Val::Px(scrolling_list.position);
}
}
}

#[cfg(feature = "bevy_dev_tools")]
// The system that will enable/disable the debug outlines around the nodes
fn toggle_overlay(
Expand Down

0 comments on commit 640e7e1

Please sign in to comment.