diff --git a/Cargo.toml b/Cargo.toml index 489e1344..ab9b4e82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ unsafe_op_in_unsafe_fn = "warn" unused_qualifications = "warn" [workspace.dependencies] -bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "af93e78b72c1e435e73c54c9d0ac6260dc170762" } +bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "eb19a9ea0b175cec25400860fee71c5efb9a4602" } thiserror = "1" serde = { version = "1", features = ["derive"] } diff --git a/bevy_editor_panes/bevy_2d_viewport/src/lib.rs b/bevy_editor_panes/bevy_2d_viewport/src/lib.rs index 784dc26b..42d50673 100644 --- a/bevy_editor_panes/bevy_2d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_2d_viewport/src/lib.rs @@ -84,20 +84,19 @@ fn on_pane_creation( let image_handle = images.add(image); let image_id = commands - .spawn(ImageBundle { - style: Style { + .spawn(( + UiImage { + texture: image_handle.clone(), + ..Default::default() + }, + Style { top: Val::ZERO, bottom: Val::ZERO, left: Val::ZERO, right: Val::ZERO, ..default() }, - image: UiImage { - texture: image_handle.clone(), - ..Default::default() - }, - ..default() - }) + )) .set_parent(content_node) .id(); diff --git a/bevy_widgets/bevy_menu_bar/src/lib.rs b/bevy_widgets/bevy_menu_bar/src/lib.rs index 012e76d3..dda0065d 100644 --- a/bevy_widgets/bevy_menu_bar/src/lib.rs +++ b/bevy_widgets/bevy_menu_bar/src/lib.rs @@ -28,8 +28,9 @@ pub struct MenuBarSet; fn menu_setup(mut commands: Commands, root: Query>, theme: Res) { commands .entity(root.single()) - .insert(NodeBundle { - style: Style { + .insert(( + Node::default(), + Style { width: Val::Percent(100.0), height: Val::Px(30.0), display: Display::Flex, @@ -45,24 +46,23 @@ fn menu_setup(mut commands: Commands, root: Query>, th }, ..Default::default() }, - background_color: theme.background_color, - ..Default::default() - }) + theme.background_color, + )) .with_children(|parent| { - parent.spawn(NodeBundle { - style: Style { + parent.spawn(( + Node::default(), + Style { width: Val::Px(30.0), height: Val::Px(20.0), ..Default::default() }, - background_color: BackgroundColor(Color::Oklaba(Oklaba { + BackgroundColor(Color::Oklaba(Oklaba { lightness: 0.090, a: 0.0, b: 0.0, alpha: 1.0, })), - ..Default::default() - }); + )); }); } diff --git a/crates/bevy_editor/src/ui.rs b/crates/bevy_editor/src/ui.rs index 9972c2ea..4839f3a5 100644 --- a/crates/bevy_editor/src/ui.rs +++ b/crates/bevy_editor/src/ui.rs @@ -32,8 +32,9 @@ fn ui_setup(mut commands: Commands, theme: Res) { )); commands - .spawn(NodeBundle { - style: Style { + .spawn(( + Node::default(), + Style { width: Val::Percent(100.0), height: Val::Percent(100.0), @@ -43,10 +44,9 @@ fn ui_setup(mut commands: Commands, theme: Res) { ..Default::default() }, - background_color: theme.background_color, - ..Default::default() - }) - .insert(RootUINode) + theme.background_color, + RootUINode, + )) .with_children(|parent| { parent.spawn(MenuBarNode); parent.spawn(RootPaneLayoutNode); diff --git a/crates/bevy_editor_camera/examples/editor_camera_2d.rs b/crates/bevy_editor_camera/examples/editor_camera_2d.rs index d4b951ed..d3ed8790 100644 --- a/crates/bevy_editor_camera/examples/editor_camera_2d.rs +++ b/crates/bevy_editor_camera/examples/editor_camera_2d.rs @@ -19,21 +19,18 @@ fn main() { } fn setup(mut commands: Commands, asset_server: Res) { - commands - .spawn(( - Camera2d, - Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), - )) - .insert(EditorCamera2d { + commands.spawn(( + Camera2d, + EditorCamera2d { pan_mouse_buttons: vec![MouseButton::Left, MouseButton::Middle, MouseButton::Right], bound: Rect { min: Vec2::new(-1000.0, -1000.0), max: Vec2::new(1000.0, 1000.0), }, - zoom_range: 0.4..=10.0, - zoom: 0.4, // Set the initial zoom level. + scale_range: 0.4..=10.0, ..Default::default() - }); + }, + )); commands.spawn(Sprite { image: asset_server.load("bevy_bird.png"), diff --git a/crates/bevy_editor_camera/src/editor_camera_2d/mod.rs b/crates/bevy_editor_camera/src/editor_camera_2d/mod.rs index c5bb5c5e..d4a7673e 100644 --- a/crates/bevy_editor_camera/src/editor_camera_2d/mod.rs +++ b/crates/bevy_editor_camera/src/editor_camera_2d/mod.rs @@ -8,8 +8,11 @@ use std::ops::RangeInclusive; use bevy::{ - input::mouse::AccumulatedMouseScroll, math::bounding::Aabb2d, prelude::*, - render::camera::ScalingMode, window::PrimaryWindow, + input::mouse::AccumulatedMouseScroll, + math::bounding::{Aabb2d, BoundingVolume}, + prelude::*, + render::camera::CameraProjection, + window::PrimaryWindow, }; /// Plugin which adds necessary components and systems for 2d editor cameras to work. @@ -39,14 +42,10 @@ pub struct EditorCamera2d { /// The bound which the camera will be clamped to when panning /// and zooming. Use infinity values to disable any clamping. pub bound: Rect, - /// The current zoom level of the camera. - /// - /// This value is clamped to the [`Self::zoom_range`]. - pub zoom: f32, /// The range of zoom the allowed zoom the camera will be clamped to. /// /// To disable clamping set the range to [`f32::NEG_INFINITY`]..=[`f32::INFINITY`]. - pub zoom_range: RangeInclusive, + pub scale_range: RangeInclusive, /// The sensitivity of the mouse wheel input when zooming. pub zoom_sensitivity: f32, /// When true, zooming the camera will center on the mouse cursor @@ -77,8 +76,7 @@ impl Default for EditorCamera2d { min: Vec2::ONE * f32::NEG_INFINITY, max: Vec2::ONE * f32::INFINITY, }, - zoom: 1.0, - zoom_range: f32::NEG_INFINITY..=f32::INFINITY, + scale_range: f32::NEG_INFINITY..=f32::INFINITY, zoom_sensitivity: 0.1, zoom_to_cursor: true, viewport_override: None, @@ -86,14 +84,27 @@ impl Default for EditorCamera2d { } } +/// Makes sure that the camera projection scale stays in the provided bounds +/// and range. +fn constrain_proj_scale( + proj: &mut OrthographicProjection, + bounded_area_size: Vec2, + scale_range: &RangeInclusive, + window_size: Vec2, +) { + proj.scale = proj.scale.clamp(*scale_range.start(), *scale_range.end()); + + // If there is both a min and max boundary, that limits how far we can zoom. + // Make sure we don't exceed that + if bounded_area_size.x.is_finite() || bounded_area_size.y.is_finite() { + let max_safe_scale = max_scale_within_bounds(bounded_area_size, proj, window_size); + proj.scale = proj.scale.min(max_safe_scale.x).min(max_safe_scale.y); + } +} + /// Clamps a camera position to a safe zone. "Safe" means that each screen /// corner is constrained to the corresponding bound corner. /// -/// # Parameters -/// - `pos` - The position if the bounded area. -/// - `bounded_area_size` - The size of the bounded area. -/// - `bound` - The bound the bounded area should stay within. -/// /// # Visual explanation /// /// This makes sure the bounded area does not go outside the bound. @@ -107,42 +118,27 @@ impl Default for EditorCamera2d { /// | +-----------------+ | /// +------------------------+ /// ``` -fn clamp_area_to_bound(pos: Vec2, bounded_area_size: Vec2, bound: Aabb2d) -> Vec2 { - // We are manually implementing the `bound.shrink()` functionality - // because the existing one is unsafe and can panic when the bounded - // area size does not fit within the bound. - let aabb = Aabb2d { - min: bound.min + bounded_area_size / 2.0, - max: bound.max - bounded_area_size / 2.0, - }; - - if aabb.min.x > aabb.max.x || aabb.min.y > aabb.max.y { - // Return center of bound - return (aabb.min + aabb.max) / 2.0; - } - +/// +/// Since bevy doesn't provide a `shrink` method on a `Rect` yet, we have to +/// operate on `Aabb2d` type. +fn clamp_to_safe_zone(pos: Vec2, aabb: Aabb2d, bounded_area_size: Vec2) -> Vec2 { + let aabb = aabb.shrink(bounded_area_size / 2.); pos.clamp(aabb.min, aabb.max) } -/// Returns the [`ScalingMode`] as a Vec2. -/// -/// If a mode provides two values they are put into x and y respectively, -/// otherwise x and y will be filled with the same value. -fn scaling_mode_as_vec2(scaling_mode: &ScalingMode) -> Vec2 { - match scaling_mode { - ScalingMode::Fixed { width, height } => Vec2::new(*width, *height), - ScalingMode::AutoMin { - min_width, - min_height, - } => Vec2::new(*min_width, *min_height), - ScalingMode::AutoMax { - max_width, - max_height, - } => Vec2::new(*max_width, *max_height), - ScalingMode::WindowSize(scale) - | ScalingMode::FixedVertical(scale) - | ScalingMode::FixedHorizontal(scale) => Vec2::new(*scale, *scale), - } +/// `max_scale_within_bounds` is used to find the maximum safe zoom out/projection +/// scale when we have been provided with minimum and maximum x boundaries for +/// the camera. +fn max_scale_within_bounds( + bounded_area_size: Vec2, + proj: &OrthographicProjection, + window_size: Vec2, //viewport? +) -> Vec2 { + let mut proj = proj.clone(); + proj.scale = 1.; + proj.update(window_size.x, window_size.y); + let base_world_size = proj.area.size(); + bounded_area_size / base_world_size } fn camera_zoom( @@ -155,42 +151,35 @@ fn camera_zoom( &mut Transform, )>, ) { + if mouse_wheel.delta.y == 0.0 { + return; + } + let Ok(window) = primary_window.get_single() else { // Log an error message once here? return; }; - for (mut e_camera, camera, mut projection, mut transform) in query.iter_mut() { + for (e_camera, camera, mut projection, mut transform) in query.iter_mut() { if !e_camera.enabled { continue; } - if mouse_wheel.delta.y == 0.0 { - continue; - } - let viewport_size = camera.logical_viewport_size().unwrap_or(window.size()); let viewport_rect = e_camera .viewport_override .unwrap_or(Rect::from_corners(Vec2::ZERO, viewport_size)); - let old_projection_scale = projection.scaling_mode; + let old_scale = projection.scale; + projection.scale *= 1. - mouse_wheel.delta.y * e_camera.zoom_sensitivity; - let scroll_delta = 1.0 - mouse_wheel.delta.y * e_camera.zoom_sensitivity; - let zoom = e_camera.zoom / scroll_delta; - - // Clamp the scroll delta so that it will never go beyond the zoom range. - let zoom_delta = if zoom < *e_camera.zoom_range.start() { - e_camera.zoom / *e_camera.zoom_range.start() - } else if zoom > *e_camera.zoom_range.end() { - e_camera.zoom / *e_camera.zoom_range.end() - } else { - scroll_delta - }; - - e_camera.zoom /= zoom_delta; - projection.scaling_mode *= zoom_delta; + constrain_proj_scale( + &mut projection, + e_camera.bound.size(), + &e_camera.scale_range, + viewport_size, + ); let cursor_normalized_viewport_pos = window .cursor_position() @@ -203,15 +192,7 @@ fn camera_zoom( ((cursor_pos - (view_pos + viewport_rect.min)) / viewport_rect.size()) * 2. - Vec2::ONE }) - .map(|p| { - Vec2::new(p.x, -p.y) - // This is a fix because somehow in window scaling mode everything is reversed. - * if let ScalingMode::WindowSize(_) = projection.scaling_mode { - -1.0 - } else { - 1.0 - } - }); + .map(|p| Vec2::new(p.x, -p.y)); // Move the camera position to normalize the projection window let (Some(cursor_normalized_view_pos), true) = @@ -220,24 +201,20 @@ fn camera_zoom( continue; }; - // This may be incorrect :/ but it seems to work good enough for now. - // We should probably handle each scaling mode specifically to get the correct result. - let proj_size = projection.area.max / scaling_mode_as_vec2(&old_projection_scale); + let proj_size = projection.area.max / old_scale; - let cursor_world_pos = transform.translation.truncate() - + cursor_normalized_view_pos * proj_size * scaling_mode_as_vec2(&old_projection_scale); + let cursor_world_pos = + transform.translation.truncate() + cursor_normalized_view_pos * projection.area.max; - let proposed_cam_pos = cursor_world_pos - - cursor_normalized_view_pos - * proj_size - * scaling_mode_as_vec2(&projection.scaling_mode); + let proposed_cam_pos = + cursor_world_pos - cursor_normalized_view_pos * proj_size * projection.scale; // As we zoom out, we don't want the viewport to move beyond the provided // boundary. If the most recent change to the camera zoom would move cause // parts of the window beyond the boundary to be shown, we need to change the // camera position to keep the viewport within bounds. transform.translation = - clamp_area_to_bound(proposed_cam_pos, projection.area.size(), e_camera.aabb()) + clamp_to_safe_zone(proposed_cam_pos, e_camera.aabb(), projection.area.size()) .extend(transform.translation.z); } } @@ -295,53 +272,9 @@ fn camera_pan( let proposed_cam_pos = transform.translation.truncate() - mouse_delta; transform.translation = - clamp_area_to_bound(proposed_cam_pos, projection_area_size, e_camera.aabb()) + clamp_to_safe_zone(proposed_cam_pos, e_camera.aabb(), projection_area_size) .extend(transform.translation.z); } *prev_mouse_pos = Some(mouse_pos); } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_clamp_area_to_bound() { - let bound = Aabb2d { - min: Vec2::new(-10.0, -10.0), - max: Vec2::new(10.0, 10.0), - }; - - let bounded_area_size = Vec2::new(5.0, 5.0); - - // Test when the bounded area is within the bound - let pos = Vec2::new(0.0, 0.0); - assert_eq!(clamp_area_to_bound(pos, bounded_area_size, bound), pos); - - // Test when the bounded area is outside the bound - let pos = Vec2::new(-10.0, -10.0); - assert_eq!( - clamp_area_to_bound(pos, bounded_area_size, bound), - Vec2::new(-7.5, -7.5) - ); - - let pos = Vec2::new(10.0, 10.0); - assert_eq!( - clamp_area_to_bound(pos, bounded_area_size, bound), - Vec2::new(7.5, 7.5) - ); - - let pos = Vec2::new(10.0, -10.0); - assert_eq!( - clamp_area_to_bound(pos, bounded_area_size, bound), - Vec2::new(7.5, -7.5) - ); - - let pos = Vec2::new(-10.0, 10.0); - assert_eq!( - clamp_area_to_bound(pos, bounded_area_size, bound), - Vec2::new(-7.5, 7.5) - ); - } -} diff --git a/crates/bevy_infinite_grid/examples/simple.rs b/crates/bevy_infinite_grid/examples/simple.rs index f71f84c8..e4d2417b 100644 --- a/crates/bevy_infinite_grid/examples/simple.rs +++ b/crates/bevy_infinite_grid/examples/simple.rs @@ -89,7 +89,7 @@ mod camera_controller { key_input: Res>, mut query: Query<(&mut Transform, &mut CameraController), With>, ) { - let dt = time.delta_seconds(); + let dt = time.delta_secs(); if let Ok((mut transform, mut state)) = query.get_single_mut() { // Handle key input diff --git a/crates/bevy_pane_layout/src/lib.rs b/crates/bevy_pane_layout/src/lib.rs index a8e6d1a4..ad4b253a 100644 --- a/crates/bevy_pane_layout/src/lib.rs +++ b/crates/bevy_pane_layout/src/lib.rs @@ -144,16 +144,16 @@ fn setup( theme: Res, panes_root: Single>, ) { - commands.entity(*panes_root).insert(NodeBundle { - background_color: theme.background_color, - style: Style { + commands.entity(*panes_root).insert(( + Node::default(), + theme.background_color, + Style { padding: UiRect::all(Val::Px(1.)), height: Val::Percent(100.), width: Val::Percent(100.), ..default() }, - ..default() - }); + )); let divider = spawn_divider(&mut commands, Divider::Horizontal, 1.) .set_parent(*panes_root) diff --git a/crates/bevy_pane_layout/src/ui.rs b/crates/bevy_pane_layout/src/ui.rs index 646e3ba2..1d40f22c 100644 --- a/crates/bevy_pane_layout/src/ui.rs +++ b/crates/bevy_pane_layout/src/ui.rs @@ -16,11 +16,9 @@ pub(crate) fn spawn_pane<'a>( // Unstyled root node let root = commands .spawn(( - NodeBundle { - style: Style { - padding: UiRect::all(Val::Px(1.5)), - ..default() - }, + Node::default(), + Style { + padding: UiRect::all(Val::Px(1.5)), ..default() }, Size(size), @@ -31,16 +29,14 @@ pub(crate) fn spawn_pane<'a>( // Area let area = commands .spawn(( - NodeBundle { - background_color: theme.pane_area_background_color, - border_radius: theme.border_radius, - style: Style { - overflow: Overflow::clip(), - width: Val::Percent(100.), - height: Val::Percent(100.), - flex_direction: FlexDirection::Column, - ..default() - }, + Node::default(), + theme.pane_area_background_color, + theme.border_radius, + Style { + overflow: Overflow::clip(), + width: Val::Percent(100.), + height: Val::Percent(100.), + flex_direction: FlexDirection::Column, ..default() }, PaneAreaNode, @@ -51,16 +47,14 @@ pub(crate) fn spawn_pane<'a>( // Header commands .spawn(( - NodeBundle { - background_color: theme.pane_header_background_color, - border_radius: theme.pane_header_border_radius, - style: Style { - padding: UiRect::axes(Val::Px(5.), Val::Px(3.)), - width: Val::Percent(100.), - height: Val::Px(27.), - align_items: AlignItems::Center, - ..default() - }, + Node::default(), + theme.pane_header_background_color, + theme.pane_header_border_radius, + Style { + padding: UiRect::axes(Val::Px(5.), Val::Px(3.)), + width: Val::Percent(100.), + height: Val::Px(27.), + align_items: AlignItems::Center, ..default() }, PaneHeaderNode, @@ -99,11 +93,9 @@ pub(crate) fn spawn_pane<'a>( // Content commands .spawn(( - NodeBundle { - style: Style { - flex_grow: 1., - ..default() - }, + Node::default(), + Style { + flex_grow: 1., ..default() }, PaneContentNode, @@ -119,13 +111,11 @@ pub(crate) fn spawn_divider<'a>( size: f32, ) -> EntityCommands<'a> { commands.spawn(( - NodeBundle { - style: Style { - flex_direction: match divider { - Divider::Horizontal => FlexDirection::Row, - Divider::Vertical => FlexDirection::Column, - }, - ..default() + Node::default(), + Style { + flex_direction: match divider { + Divider::Horizontal => FlexDirection::Row, + Divider::Vertical => FlexDirection::Column, }, ..default() }, @@ -140,8 +130,9 @@ pub(crate) fn spawn_resize_handle<'a>( ) -> EntityCommands<'a> { const SIZE: f32 = 7.; // Add a root node with zero size along the divider axis to avoid messing up the layout - let mut ec = commands.spawn(NodeBundle { - style: Style { + let mut ec = commands.spawn(( + Node::default(), + Style { width: match divider_parent { Divider::Horizontal => Val::Px(SIZE), Divider::Vertical => Val::Percent(100.), @@ -158,22 +149,19 @@ pub(crate) fn spawn_resize_handle<'a>( }, ..default() }, - z_index: ZIndex(3), - ..default() - }); + ZIndex(3), + )); // Add the Resize ec.with_child(( - NodeBundle { - style: Style { - width: match divider_parent { - Divider::Horizontal => Val::Px(SIZE), - Divider::Vertical => Val::Percent(100.), - }, - height: match divider_parent { - Divider::Horizontal => Val::Percent(100.), - Divider::Vertical => Val::Px(SIZE), - }, - ..default() + Node::default(), + Style { + width: match divider_parent { + Divider::Horizontal => Val::Px(SIZE), + Divider::Vertical => Val::Percent(100.), + }, + height: match divider_parent { + Divider::Horizontal => Val::Percent(100.), + Divider::Vertical => Val::Px(SIZE), }, ..default() }, diff --git a/crates/bevy_undo/examples/cube_move.rs b/crates/bevy_undo/examples/cube_move.rs index 31ec7da1..307616da 100644 --- a/crates/bevy_undo/examples/cube_move.rs +++ b/crates/bevy_undo/examples/cube_move.rs @@ -74,16 +74,16 @@ fn setup( ..default() }); - cmd.spawn(NodeBundle { - style: Style { + cmd.spawn(( + Node::default(), + Style { width: Val::Percent(100.0), height: Val::Percent(100.0), justify_content: JustifyContent::Start, align_items: AlignItems::Start, ..default() }, - ..default() - }) + )) .with_children(|parent| { parent.spawn(TextBundle { text: Text { diff --git a/projects/alien_cake_addict/src/main.rs b/projects/alien_cake_addict/src/main.rs index df379edf..35b3cdce 100644 --- a/projects/alien_cake_addict/src/main.rs +++ b/projects/alien_cake_addict/src/main.rs @@ -299,7 +299,7 @@ fn focus_camera( // smooth out the camera movement using the frame time let mut camera_motion = game.camera_should_focus - game.camera_is_focus; if camera_motion.length() > 0.2 { - camera_motion *= SPEED * time.delta_seconds(); + camera_motion *= SPEED * time.delta_secs(); // set the new camera's actual focus game.camera_is_focus += camera_motion; } @@ -368,10 +368,9 @@ fn spawn_bonus( fn rotate_bonus(game: Res, time: Res