From 2aeb9f0b143970bed0fbefff00f4584fc41dd33e Mon Sep 17 00:00:00 2001 From: okhai <57156589+okhaimie-dev@users.noreply.github.com> Date: Thu, 17 Jul 2025 05:17:03 -0500 Subject: [PATCH 01/30] Add advanced entity outline calculations (#222) * Add advanced entity outline calculations The changes add sophisticated AABB (Axis-Aligned Bounding Box) calculations for accurately drawing outlines around selected entities, handling various entity types like meshes and sprites, and properly accounting for hierarchies and transformations. * minor changes * Extract outline gizmo queries into SystemParam struct The change extracts multiple queries used in the outline gizmo system into a dedicated `OutlineGizmoQueries` struct that implements `SystemParam` * fmt * Rename outline gizmo plugin to selection box plugin * Rename outline gizmo to selection box The changes are focused on renaming the outline gizmo functionality to selection box, which includes renaming the module, component, functions and UI text for better clarity and consistency. --- Cargo.toml | 1 + bevy_editor_panes/bevy_3d_viewport/Cargo.toml | 1 + bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 6 +- .../bevy_3d_viewport/src/outline_gizmo.rs | 81 ----- .../bevy_3d_viewport/src/selection_box.rs | 296 ++++++++++++++++++ 5 files changed, 301 insertions(+), 84 deletions(-) delete mode 100644 bevy_editor_panes/bevy_3d_viewport/src/outline_gizmo.rs create mode 100644 bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs diff --git a/Cargo.toml b/Cargo.toml index a9a5a24a..bec090f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ unused_qualifications = "warn" bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14", features = [ "wayland", ] } +bevy_render = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14" } bevy_derive = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14" } bevy_macro_utils = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14" } thiserror = "2.0" diff --git a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml index 7bb80d70..102a1a54 100644 --- a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml +++ b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml @@ -10,6 +10,7 @@ bevy_editor_cam.workspace = true bevy_editor_styles.workspace = true bevy_infinite_grid.workspace = true bevy_editor_core.workspace = true +bevy_render.workspace = true [lints] workspace = true diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index 1a9e2760..e1989f8f 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -18,9 +18,9 @@ use bevy_infinite_grid::{InfiniteGrid, InfiniteGridPlugin, InfiniteGridSettings} use bevy_pane_layout::prelude::*; use view_gizmo::{spawn_view_gizmo_target_texture, ViewGizmoPlugin}; -use crate::outline_gizmo::OutlineGizmoPlugin; +use crate::selection_box::SelectionBoxPlugin; -mod outline_gizmo; +mod selection_box; mod view_gizmo; /// The identifier for the 3D Viewport. @@ -47,7 +47,7 @@ impl Plugin for Viewport3dPanePlugin { app.add_plugins(InfiniteGridPlugin); } - app.add_plugins((DefaultEditorCamPlugins, ViewGizmoPlugin, OutlineGizmoPlugin)) + app.add_plugins((DefaultEditorCamPlugins, ViewGizmoPlugin, SelectionBoxPlugin)) .add_systems(Startup, setup) .add_systems( PreUpdate, diff --git a/bevy_editor_panes/bevy_3d_viewport/src/outline_gizmo.rs b/bevy_editor_panes/bevy_3d_viewport/src/outline_gizmo.rs deleted file mode 100644 index 824a55c6..00000000 --- a/bevy_editor_panes/bevy_3d_viewport/src/outline_gizmo.rs +++ /dev/null @@ -1,81 +0,0 @@ -use bevy::prelude::*; -use bevy_editor_core::SelectedEntity; - -pub struct OutlineGizmoPlugin; -impl Plugin for OutlineGizmoPlugin { - fn build(&self, app: &mut App) { - app.init_resource::() - .add_systems(Startup, spawn_gizmo_toggle_ui) - .add_systems(Update, outline_gizmo_system) - .add_systems(Update, update_gizmo_toggle_text); - } -} - -#[derive(Resource, Default)] -pub struct ShowOutlines(pub bool); - -// Marker for the toggle button text -#[derive(Component)] -struct GizmoToggleText; - -pub fn outline_gizmo_system( - show: Res, - query: Query<&Transform>, - selected_entity: Res, - mut gizmos: Gizmos, -) { - if !show.0 { - return; - } - if let Some(entity) = selected_entity.0 { - if let Ok(transform) = query.get(entity) { - gizmos.cuboid(*transform, Color::srgb(1.0, 0.0, 0.0)); - } - } -} - -pub fn spawn_gizmo_toggle_ui(mut commands: Commands) { - info!("Spawning Gizmo Toggle UI"); - commands - .spawn(( - Node { - position_type: PositionType::Absolute, - top: Val::Px(20.0), - right: Val::Px(20.0), - width: Val::Px(100.0), - height: Val::Px(15.0), - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - ..default() - }, - BackgroundColor(Color::srgb(0.2, 0.2, 0.2)), - )) - .with_children(|parent| { - parent.spawn(( - Text::new("Show Outlines"), - TextFont::from_font_size(10.0), - GizmoToggleText, - )); - }) - .observe( - |_trigger: On>, mut show_outlines: ResMut| { - show_outlines.0 = !show_outlines.0; - }, - ); -} - -// System to update the button text when ShowOutlines changes -fn update_gizmo_toggle_text( - show_outlines: Res, - mut query: Query<&mut Text, With>, -) { - if show_outlines.is_changed() { - for mut text in &mut query { - text.0 = if show_outlines.0 { - "Hide Outlines".into() - } else { - "Show Outlines".into() - }; - } - } -} diff --git a/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs b/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs new file mode 100644 index 00000000..bc968335 --- /dev/null +++ b/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs @@ -0,0 +1,296 @@ +use bevy::ecs::system::SystemParam; +use bevy::prelude::*; +use bevy_editor_core::SelectedEntity; +use bevy_render::primitives::Aabb; + +#[derive(SystemParam)] +pub struct SelectionBoxQueries<'w, 's> { + pub mesh_query: Query< + 'w, + 's, + ( + &'static GlobalTransform, + &'static Mesh3d, + Option<&'static Aabb>, + ), + >, + pub sprite_query: Query<'w, 's, (&'static GlobalTransform, &'static Sprite), Without>, + pub aabb_query: Query< + 'w, + 's, + (&'static GlobalTransform, &'static Aabb), + (Without, Without), + >, + pub children_query: Query<'w, 's, &'static Children>, + pub transform_query: + Query<'w, 's, &'static GlobalTransform, (Without, Without, Without)>, +} + +pub struct SelectionBoxPlugin; +impl Plugin for SelectionBoxPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .add_systems(Startup, spawn_selection_box_toggle_ui) + .add_systems(Update, selection_box_system) + .add_systems(Update, update_selection_box_toggle_text); + } +} + +#[derive(Resource, Default)] +pub struct ShowSelectionBox(pub bool); + +// Marker for the toggle button text +#[derive(Component)] +struct SelectionBoxToggleText; + +/// Draw an outline for a world-space AABB +fn draw_selection_box(gizmos: &mut Gizmos, aabb: &Aabb) { + let min = aabb.min(); + let max = aabb.max(); + let center = (min + max) * 0.5; + let size = max - min; + + // Draw the cuboid outline at the AABB center with the AABB size + let outline_transform = Transform::from_translation(center.into()).with_scale(size.into()); + + gizmos.cuboid(outline_transform, Color::srgb(1.0, 0.5, 0.0)); // Orange outline +} + +/// Fallback outline for entities without proper bounds +fn draw_fallback_selection_box(gizmos: &mut Gizmos, global_transform: &GlobalTransform) { + let translation = global_transform.translation(); + let default_size = Vec3::splat(1.0); + + let outline_transform = Transform::from_translation(translation).with_scale(default_size); + + gizmos.cuboid(outline_transform, Color::srgb(0.5, 0.5, 0.5)); // Gray outline +} + +pub fn selection_box_system( + show: Res, + selected_entity: Res, + mut gizmos: Gizmos, + queries: SelectionBoxQueries, + meshes: Res>, +) { + if !show.0 { + return; + } + + let Some(entity) = selected_entity.0 else { + return; + }; + + // Calculate the bounding box for the entity (including children) + if let Some(world_aabb) = calculate_world_aabb( + entity, + &queries.mesh_query, + &queries.sprite_query, + &queries.aabb_query, + &queries.children_query, + &queries.transform_query, + &meshes, + ) { + draw_selection_box(&mut gizmos, &world_aabb); + } else { + // Fallback to simple transform-based selection box + if let Ok(global_transform) = queries.transform_query.get(entity) { + draw_fallback_selection_box(&mut gizmos, global_transform); + } + } +} + +/// Calculate the world-space AABB for an entity and optionally its children +fn calculate_world_aabb( + entity: Entity, + mesh_query: &Query<(&GlobalTransform, &Mesh3d, Option<&Aabb>)>, + sprite_query: &Query<(&GlobalTransform, &Sprite), Without>, + aabb_query: &Query<(&GlobalTransform, &Aabb), (Without, Without)>, + children_query: &Query<&Children>, + transform_query: &Query<&GlobalTransform, (Without, Without, Without)>, + meshes: &Assets, +) -> Option { + let mut combined_aabb: Option = None; + + // Helper function to combine AABBs + let mut combine_aabb = |new_aabb: Aabb| { + if let Some(existing) = combined_aabb { + combined_aabb = Some(combine_aabbs(&existing, &new_aabb)); + } else { + combined_aabb = Some(new_aabb); + } + }; + + // Try to get AABB from the entity itself + if let Some(entity_aabb) = get_entity_aabb( + entity, + mesh_query, + sprite_query, + aabb_query, + transform_query, + meshes, + ) { + combine_aabb(entity_aabb); + } + + // Recursively include children's AABBs + if let Ok(children) = children_query.get(entity) { + for &child in children { + if let Some(child_aabb) = calculate_world_aabb( + child, + mesh_query, + sprite_query, + aabb_query, + children_query, + transform_query, + meshes, + ) { + combine_aabb(child_aabb); + } + } + } + + combined_aabb +} + +/// Combine two AABBs into a single AABB that encompasses both +fn combine_aabbs(a: &Aabb, b: &Aabb) -> Aabb { + let min = a.min().min(b.min()); + let max = a.max().max(b.max()); + Aabb::from_min_max(min.into(), max.into()) +} + +/// Get the AABB for a single entity +fn get_entity_aabb( + entity: Entity, + mesh_query: &Query<(&GlobalTransform, &Mesh3d, Option<&Aabb>)>, + sprite_query: &Query<(&GlobalTransform, &Sprite), Without>, + aabb_query: &Query<(&GlobalTransform, &Aabb), (Without, Without)>, + transform_query: &Query<&GlobalTransform, (Without, Without, Without)>, + meshes: &Assets, +) -> Option { + // Try mesh entities first + if let Ok((global_transform, mesh_handle, existing_aabb)) = mesh_query.get(entity) { + // Use existing AABB if available, otherwise compute from mesh + let local_aabb = if let Some(aabb) = existing_aabb { + *aabb + } else if let Some(_mesh) = meshes.get(&mesh_handle.0) { + // TODO: Compute AABB from mesh if possible + Aabb::from_min_max(Vec3::splat(-0.5), Vec3::splat(0.5)) + } else { + return None; + }; + + return Some(transform_aabb(&local_aabb, global_transform)); + } + + // Try sprite entities + if let Ok((global_transform, sprite)) = sprite_query.get(entity) { + let size = sprite.custom_size.unwrap_or(Vec2::new(1.0, 1.0)); + let local_aabb = Aabb::from_min_max( + Vec3::new(-size.x * 0.5, -size.y * 0.5, -0.01), + Vec3::new(size.x * 0.5, size.y * 0.5, 0.01), + ); + return Some(transform_aabb(&local_aabb, global_transform)); + } + + // Try entities with existing AABB components + if let Ok((global_transform, aabb)) = aabb_query.get(entity) { + return Some(transform_aabb(aabb, global_transform)); + } + + // Fallback for entities with just transforms + if let Ok(global_transform) = transform_query.get(entity) { + let default_size = 0.5; + let local_aabb = Aabb::from_min_max(Vec3::splat(-default_size), Vec3::splat(default_size)); + return Some(transform_aabb(&local_aabb, global_transform)); + } + + None +} + +/// Transform a local AABB to world space using `GlobalTransform` +fn transform_aabb(local_aabb: &Aabb, global_transform: &GlobalTransform) -> Aabb { + let (scale, rotation, translation) = global_transform.to_scale_rotation_translation(); + + // Get the 8 corners of the AABB + let min = local_aabb.min(); + let max = local_aabb.max(); + let corners = [ + Vec3::new(min.x, min.y, min.z), + Vec3::new(max.x, min.y, min.z), + Vec3::new(min.x, max.y, min.z), + Vec3::new(max.x, max.y, min.z), + Vec3::new(min.x, min.y, max.z), + Vec3::new(max.x, min.y, max.z), + Vec3::new(min.x, max.y, max.z), + Vec3::new(max.x, max.y, max.z), + ]; + + // Transform all corners to world space + let transformed_corners: Vec = corners + .iter() + .map(|&corner| { + let scaled = corner * scale; + let rotated = rotation * scaled; + rotated + translation + }) + .collect(); + + // Find the min/max of transformed corners + let mut world_min = transformed_corners[0]; + let mut world_max = transformed_corners[0]; + + for &corner in &transformed_corners[1..] { + world_min = world_min.min(corner); + world_max = world_max.max(corner); + } + + Aabb::from_min_max(world_min, world_max) +} + +pub fn spawn_selection_box_toggle_ui(mut commands: Commands) { + info!("Spawning Selection Box Toggle UI"); + commands + .spawn(( + Node { + position_type: PositionType::Absolute, + top: Val::Px(20.0), + right: Val::Px(20.0), + width: Val::Px(100.0), + height: Val::Px(15.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + ..default() + }, + BackgroundColor(Color::srgb(0.2, 0.2, 0.2)), + )) + .with_children(|parent| { + parent.spawn(( + Text::new("Show Selection Box"), + TextFont::from_font_size(10.0), + SelectionBoxToggleText, + )); + }) + .observe( + |_trigger: On>, mut show_selection: ResMut| { + show_selection.0 = !show_selection.0; + }, + ); +} + +// System to update the button text when ShowSelectionBox changes +fn update_selection_box_toggle_text( + show_selection: Res, + mut query: Query<&mut Text, With>, +) { + if show_selection.is_changed() { + for mut text in &mut query { + text.0 = if show_selection.0 { + "Hide Selection Box".into() + } else { + "Show Selection Box".into() + }; + } + } +} From 9d0d202aafe982a7034c918604934a5b5be8f890 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 17 Jul 2025 21:22:29 +0200 Subject: [PATCH 02/30] Launcher code quality improvements (#223) --- crates/bevy_editor_launcher/src/main.rs | 11 +++-------- crates/bevy_editor_launcher/src/ui.rs | 7 +------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/crates/bevy_editor_launcher/src/main.rs b/crates/bevy_editor_launcher/src/main.rs index 13f07189..aecdffd7 100644 --- a/crates/bevy_editor_launcher/src/main.rs +++ b/crates/bevy_editor_launcher/src/main.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use bevy::{ + ecs::schedule::common_conditions::any_with_component, prelude::*, tasks::{block_on, futures_lite::future, IoTaskPool, Task}, }; @@ -23,11 +24,6 @@ mod ui; #[derive(Component)] struct CreateProjectTask(Task>); -/// A utils to run a system only if the [`CreateProjectTask`] is running -fn run_if_task_is_running(task_query: Query>) -> bool { - task_query.iter().count() > 0 -} - /// Check on the status of the [`CreateProjectTask`] and handle the result when done fn poll_create_project_task( mut commands: Commands, @@ -49,7 +45,6 @@ fn poll_create_project_task( let (project_list_entity, children) = query.iter().next().unwrap(); let plus_button_entity = children.last().unwrap(); - commands.entity(*plus_button_entity).remove::(); commands .entity(project_list_entity) .with_children(|builder| { @@ -70,7 +65,7 @@ fn poll_create_project_task( /// Spawn a new [`CreateProjectTask`] to create a new project fn spawn_create_new_project_task(commands: &mut Commands, template: Templates, path: PathBuf) { let task = IoTaskPool::get().spawn(async move { create_new_project(template, path).await }); - commands.spawn_empty().insert(CreateProjectTask(task)); + commands.spawn(CreateProjectTask(task)); } #[derive(Resource)] @@ -95,7 +90,7 @@ fn main() { .add_systems( Update, ( - poll_create_project_task.run_if(run_if_task_is_running), + poll_create_project_task.run_if(any_with_component::), ui::handle_notification_popups, ), ) diff --git a/crates/bevy_editor_launcher/src/ui.rs b/crates/bevy_editor_launcher/src/ui.rs index 79919720..18379073 100644 --- a/crates/bevy_editor_launcher/src/ui.rs +++ b/crates/bevy_editor_launcher/src/ui.rs @@ -139,12 +139,7 @@ pub fn setup( border: UiRect::all(Val::Px(5.0)), ..default() }, - BorderRadius::new( - Val::Px(20.0), - Val::Px(20.0), - Val::Px(20.0), - Val::Px(20.0), - ), + BorderRadius::all(Val::Px(20.0)), BorderColor::all(theme.button.background_color.0), )) .with_child(( From 617ea6d7dc1f900f6b6faecb6e74ae251c559e62 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 17 Jul 2025 21:35:37 +0200 Subject: [PATCH 03/30] Simplify `selection_box::transform_aabb` (#224) --- .../bevy_3d_viewport/src/selection_box.rs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs b/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs index bc968335..c56fe45e 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs @@ -211,8 +211,6 @@ fn get_entity_aabb( /// Transform a local AABB to world space using `GlobalTransform` fn transform_aabb(local_aabb: &Aabb, global_transform: &GlobalTransform) -> Aabb { - let (scale, rotation, translation) = global_transform.to_scale_rotation_translation(); - // Get the 8 corners of the AABB let min = local_aabb.min(); let max = local_aabb.max(); @@ -225,23 +223,14 @@ fn transform_aabb(local_aabb: &Aabb, global_transform: &GlobalTransform) -> Aabb Vec3::new(max.x, min.y, max.z), Vec3::new(min.x, max.y, max.z), Vec3::new(max.x, max.y, max.z), - ]; - - // Transform all corners to world space - let transformed_corners: Vec = corners - .iter() - .map(|&corner| { - let scaled = corner * scale; - let rotated = rotation * scaled; - rotated + translation - }) - .collect(); + ] + .map(|corner| global_transform.transform_point(corner)); // Find the min/max of transformed corners - let mut world_min = transformed_corners[0]; - let mut world_max = transformed_corners[0]; + let mut world_min = corners[0]; + let mut world_max = corners[0]; - for &corner in &transformed_corners[1..] { + for &corner in &corners[1..] { world_min = world_min.min(corner); world_max = world_max.max(corner); } From 3d1823df5406fa61a874398c4245ecbc1df1fd0e Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 18 Jul 2025 05:19:51 +0200 Subject: [PATCH 04/30] Fix panel layout exploding when panels overflow (#227) --- crates/bevy_pane_layout/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pane_layout/src/lib.rs b/crates/bevy_pane_layout/src/lib.rs index fbb04a09..54db3f2d 100644 --- a/crates/bevy_pane_layout/src/lib.rs +++ b/crates/bevy_pane_layout/src/lib.rs @@ -102,7 +102,9 @@ fn setup( padding: UiRect::all(Val::Px(1.)), flex_grow: 1., width: Val::Percent(100.), - + height: Val::Percent(100.), + // Prevent children from expanding the height of this node. + min_height: Val::Px(0.), ..default() }, theme.general.background_color, From 235501a524491aa68df63325ca3347b4fd45f1f1 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 18 Jul 2025 05:20:27 +0200 Subject: [PATCH 05/30] Fix 3d viewport picking passthrough and camera input (#226) * Fix 3d viewport picking passthrough and camera input * Adjust docs --- bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 65 ++++++----- crates/bevy_editor/src/lib.rs | 7 ++ crates/bevy_editor_cam/src/input.rs | 103 +++++++++--------- 3 files changed, 93 insertions(+), 82 deletions(-) diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index e1989f8f..c0202737 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -1,7 +1,9 @@ //! 3D Viewport for Bevy use bevy::{ + asset::uuid::Uuid, picking::{ - pointer::{Location, PointerId, PointerInput, PointerLocation}, + input::{mouse_pick_events, touch_pick_events}, + pointer::{Location, PointerId, PointerInput}, PickingSystems, }, prelude::*, @@ -50,8 +52,11 @@ impl Plugin for Viewport3dPanePlugin { app.add_plugins((DefaultEditorCamPlugins, ViewGizmoPlugin, SelectionBoxPlugin)) .add_systems(Startup, setup) .add_systems( - PreUpdate, - render_target_picking_passthrough.in_set(PickingSystems::Last), + First, + render_target_picking_passthrough + .in_set(PickingSystems::Input) + .after(touch_pick_events) + .after(mouse_pick_events), ) .add_systems( PostUpdate, @@ -72,23 +77,24 @@ impl Plugin for Viewport3dPanePlugin { } } +/// A viewport is considered active while the mouse is hovering over it. #[derive(Component)] struct Active; -// TODO This does not properly handle multiple windows. -/// Copies picking events and moves pointers through render-targets. +// FIXME: This system makes a lot of assumptions and is therefore rather fragile. Does not handle multiple windows. +/// Sends copies of [`PointerInput`] event actions from the mouse pointer to pointers belonging to the viewport panes. fn render_target_picking_passthrough( - mut commands: Commands, viewports: Query<(Entity, &Bevy3dViewport)>, content: Query<&PaneContentNode>, children_query: Query<&Children>, node_query: Query<(&ComputedNode, &UiGlobalTransform, &ImageNode), With>, - mut pointers: Query<(&PointerId, &mut PointerLocation)>, mut pointer_input_reader: EventReader, + // Using commands to output PointerInput events to avoid clashing with the EventReader + mut commands: Commands, ) { for event in pointer_input_reader.read() { - // Ignore the events we send to the render-targets - if !matches!(event.location.target, NormalizedRenderTarget::Window(..)) { + // Ignore the events sent from this system by only copying events that come directly from the mouse. + if event.pointer_id != PointerId::Mouse { continue; } for (pane_root, _viewport) in &viewports { @@ -98,33 +104,21 @@ fn render_target_picking_passthrough( .unwrap(); let image_id = children_query.get(content_node_id).unwrap()[0]; - let Ok((computed_node, global_transform, ui_image)) = node_query.get(image_id) else { // Inactive viewport continue; }; - let node_rect = - Rect::from_center_size(global_transform.translation, computed_node.size()); - - let new_location = Location { - position: event.location.position - node_rect.min, - target: NormalizedRenderTarget::Image(ui_image.image.clone().into()), + let node_top_left = global_transform.translation - computed_node.size() / 2.; + let position = event.location.position - node_top_left; + let target = NormalizedRenderTarget::Image(ui_image.image.clone().into()); + + let event_copy = PointerInput { + action: event.action, + location: Location { position, target }, + pointer_id: pointer_id_from_entity(pane_root), }; - // Duplicate the event - let mut new_event = event.clone(); - // Relocate the event to the render-target - new_event.location = new_location.clone(); - // Resend the event - commands.send_event(new_event); - - if let Some((_id, mut pointer_location)) = pointers - .iter_mut() - .find(|(pointer_id, _)| **pointer_id == event.pointer_id) - { - // Relocate the pointer to the render-target - pointer_location.location = Some(new_location); - } + commands.send_event(event_copy); } } } @@ -143,6 +137,12 @@ fn setup(mut commands: Commands, theme: Res) { )); } +/// Construct a pointer id from an entity. Used to tie the viewport panel root entity to a pointer id. +fn pointer_id_from_entity(entity: Entity) -> PointerId { + let bits = entity.to_bits(); + PointerId::Custom(Uuid::from_u64_pair(bits, bits)) +} + fn on_pane_creation( structure: In, mut commands: Commands, @@ -156,6 +156,10 @@ fn on_pane_creation( let image_handle = images.add(image); + // Spawn the cursor associated with this viewport pane. + let pointer_id = pointer_id_from_entity(structure.root); + commands.spawn((pointer_id, ChildOf(structure.root))); + commands .spawn(( ImageNode::new(image_handle.clone()), @@ -190,6 +194,7 @@ fn on_pane_creation( EditorCam::default(), Transform::from_translation(Vec3::ONE * 5.).looking_at(Vec3::ZERO, Vec3::Y), RenderLayers::from_layers(&[0, 1]), + MeshPickingCamera, )) .id(); diff --git a/crates/bevy_editor/src/lib.rs b/crates/bevy_editor/src/lib.rs index 63f8970c..3b247b4a 100644 --- a/crates/bevy_editor/src/lib.rs +++ b/crates/bevy_editor/src/lib.rs @@ -58,7 +58,13 @@ impl Plugin for EditorPlugin { ui::EditorUIPlugin, AssetBrowserPanePlugin, LoadGltfPlugin, + MeshPickingPlugin, )) + .insert_resource(MeshPickingSettings { + // Workaround for the Mesh2d circle blocking picking in the 3d viewport (even though it is not visible). + require_markers: true, + ..default() + }) .add_systems(Startup, dummy_setup); } } @@ -106,6 +112,7 @@ fn dummy_setup( Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.5)))), MeshMaterial3d(materials_3d.add(Color::WHITE)), Name::new("Plane"), + Pickable::default(), )); commands.spawn(( diff --git a/crates/bevy_editor_cam/src/input.rs b/crates/bevy_editor_cam/src/input.rs index 02ff2cc8..e42c5ade 100644 --- a/crates/bevy_editor_cam/src/input.rs +++ b/crates/bevy_editor_cam/src/input.rs @@ -79,63 +79,62 @@ pub fn default_camera_inputs( let pan_start = MouseButton::Left; let zoom_stop = 0.0; - if let Some(&camera) = pointer_map.get(&PointerId::Mouse) { - let camera_query = cameras.get(camera).ok(); - let is_in_zoom_mode = camera_query - .map(|(.., editor_cam)| editor_cam.current_motion.is_zooming_only()) - .unwrap_or_default(); - let zoom_amount_abs = camera_query - .and_then(|(.., editor_cam)| { - editor_cam - .current_motion - .inputs() - .map(|inputs| inputs.zoom_velocity_abs(editor_cam.smoothing.zoom.mul_f32(2.0))) - }) - .unwrap_or(0.0); - let should_zoom_end = is_in_zoom_mode && zoom_amount_abs <= zoom_stop; - - if mouse_input.any_just_released([orbit_start, pan_start]) || should_zoom_end { - controller.write(EditorCamInputEvent::End { camera }); - } - } - for (&pointer, pointer_location) in pointers .iter() .filter_map(|(id, loc)| loc.location().map(|loc| (id, loc))) { - match pointer { - PointerId::Mouse => { - let Some((camera, ..)) = cameras.iter().find(|(_, camera, _)| { - pointer_location.is_in_viewport(camera, &primary_window) - }) else { - continue; // Pointer must be in viewport to start a motion. - }; + if matches!(pointer, PointerId::Touch(_) | PointerId::Mouse) { + continue; + } - if mouse_input.just_pressed(orbit_start) { - controller.write(EditorCamInputEvent::Start { - kind: MotionKind::OrbitZoom, - camera, - pointer, - }); - } else if mouse_input.just_pressed(pan_start) { - controller.write(EditorCamInputEvent::Start { - kind: MotionKind::PanZoom, - camera, - pointer, - }); - } else if mouse_wheel.read().map(|mw| mw.y.abs()).sum::() > 0.0 { - // Note we can't just check if the mouse wheel inputs are empty, we need to - // check if the y value abs greater than zero, otherwise we get a bunch of false - // positives, which can cause issues with figuring out what the user is trying - // to do. - controller.write(EditorCamInputEvent::Start { - kind: MotionKind::Zoom, - camera, - pointer, - }); - } + if let Some(&camera) = pointer_map.get(&pointer) { + let camera_query = cameras.get(camera).ok(); + let is_in_zoom_mode = camera_query + .map(|(.., editor_cam)| editor_cam.current_motion.is_zooming_only()) + .unwrap_or_default(); + let zoom_amount_abs = camera_query + .and_then(|(.., editor_cam)| { + editor_cam.current_motion.inputs().map(|inputs| { + inputs.zoom_velocity_abs(editor_cam.smoothing.zoom.mul_f32(2.0)) + }) + }) + .unwrap_or(0.0); + let should_zoom_end = is_in_zoom_mode && zoom_amount_abs <= zoom_stop; + + if mouse_input.any_just_released([orbit_start, pan_start]) || should_zoom_end { + controller.write(EditorCamInputEvent::End { camera }); } - PointerId::Touch(_) | PointerId::Custom(_) => continue, + } + + let Some((camera, ..)) = cameras + .iter() + .find(|(_, camera, _)| pointer_location.is_in_viewport(camera, &primary_window)) + else { + continue; // Pointer must be in viewport to start a motion. + }; + + if mouse_input.just_pressed(orbit_start) { + controller.write(EditorCamInputEvent::Start { + kind: MotionKind::OrbitZoom, + camera, + pointer, + }); + } else if mouse_input.just_pressed(pan_start) { + controller.write(EditorCamInputEvent::Start { + kind: MotionKind::PanZoom, + camera, + pointer, + }); + } else if mouse_wheel.read().map(|mw| mw.y.abs()).sum::() > 0.0 { + // Note we can't just check if the mouse wheel inputs are empty, we need to + // check if the y value abs greater than zero, otherwise we get a bunch of false + // positives, which can cause issues with figuring out what the user is trying + // to do. + controller.write(EditorCamInputEvent::Start { + kind: MotionKind::Zoom, + camera, + pointer, + }); } } @@ -293,7 +292,7 @@ impl EditorCamInputEvent { let zoom_amount = match pointer { // TODO: add pinch zoom support, probably in bevy_picking - PointerId::Mouse => mouse_wheel + PointerId::Mouse | PointerId::Custom(_) => mouse_wheel .read() .map(|mw| { let scroll_multiplier = match mw.unit { From 1a4b8b60944519c1455b4c61f649c569670292d5 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 18 Jul 2025 14:48:09 +0200 Subject: [PATCH 06/30] Update to rust edition 2024 (#228) * `cargo fix --edition` minus some unwanted changes * Change edition to 2024 * `cargo format --all` --- bevy_editor_panes/bevy_2d_viewport/Cargo.toml | 2 +- bevy_editor_panes/bevy_3d_viewport/Cargo.toml | 2 +- bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 4 ++-- bevy_editor_panes/bevy_asset_browser/Cargo.toml | 2 +- .../bevy_asset_browser/src/io/task.rs | 2 +- bevy_editor_panes/bevy_asset_browser/src/lib.rs | 5 ++--- .../src/ui/directory_content.rs | 4 ++-- .../bevy_asset_browser/src/ui/nodes.rs | 4 ++-- .../bevy_asset_browser/src/ui/top_bar.rs | 2 +- .../bevy_marketplace_viewer/Cargo.toml | 4 ++-- bevy_editor_panes/bevy_preferences/Cargo.toml | 2 +- .../bevy_properties_pane/Cargo.toml | 2 +- .../bevy_properties_pane/src/lib.rs | 2 +- bevy_editor_panes/bevy_scene_tree/Cargo.toml | 2 +- bevy_editor_panes/bevy_scene_tree/src/lib.rs | 2 +- bevy_widgets/bevy_color_picker/Cargo.toml | 2 +- bevy_widgets/bevy_command_palette/Cargo.toml | 2 +- bevy_widgets/bevy_context_menu/Cargo.toml | 2 +- bevy_widgets/bevy_field_forms/Cargo.toml | 2 +- .../bevy_field_forms/examples/nickname.rs | 6 ++++-- .../bevy_field_forms/examples/numeric_fields.rs | 2 +- .../bevy_field_forms/src/text_event_mirror.rs | 2 +- bevy_widgets/bevy_footer_bar/Cargo.toml | 2 +- .../bevy_i-cant-believe-its-not-bsn/Cargo.toml | 2 +- bevy_widgets/bevy_menu_bar/Cargo.toml | 2 +- bevy_widgets/bevy_scroll_box/Cargo.toml | 3 +-- bevy_widgets/bevy_text_editing/Cargo.toml | 2 +- .../bevy_text_editing/src/editable_text_line.rs | 6 +++--- .../bevy_text_editing/src/text_change.rs | 2 +- bevy_widgets/bevy_toolbar/Cargo.toml | 2 +- bevy_widgets/bevy_tooltips/Cargo.toml | 2 +- crates/bevy_asset_preview/Cargo.toml | 4 ++-- crates/bevy_clipboard/Cargo.toml | 2 +- crates/bevy_editor/Cargo.toml | 2 +- crates/bevy_editor/src/load_gltf.rs | 2 +- crates/bevy_editor/src/project/mod.rs | 2 +- crates/bevy_editor_cam/Cargo.toml | 2 +- crates/bevy_editor_cam/examples/map.rs | 11 ++++++----- .../bevy_editor_cam/src/controller/component.rs | 12 +++++------- crates/bevy_editor_cam/src/controller/inputs.rs | 14 +++----------- .../bevy_editor_cam/src/controller/momentum.rs | 8 ++------ .../bevy_editor_cam/src/extensions/dolly_zoom.rs | 2 +- .../src/extensions/independent_skybox.rs | 2 +- crates/bevy_editor_cam/src/extensions/look_to.rs | 2 +- crates/bevy_editor_cam/src/input.rs | 4 ++-- crates/bevy_editor_cam/src/lib.rs | 4 ++-- crates/bevy_editor_camera/Cargo.toml | 2 +- .../src/editor_camera_2d/mod.rs | 2 +- crates/bevy_editor_core/Cargo.toml | 2 +- crates/bevy_editor_launcher/Cargo.toml | 2 +- crates/bevy_editor_launcher/src/main.rs | 4 ++-- crates/bevy_editor_launcher/src/ui.rs | 2 +- crates/bevy_editor_settings/Cargo.toml | 2 +- .../src/file_system/de/list.rs | 2 +- .../src/file_system/de/mod.rs | 2 +- .../src/file_system/de/structs.rs | 2 +- .../src/file_system/de/tuple.rs | 2 +- .../src/file_system/de/tuple_struct.rs | 2 +- crates/bevy_editor_styles/Cargo.toml | 2 +- crates/bevy_infinite_grid/Cargo.toml | 2 +- crates/bevy_infinite_grid/src/lib.rs | 2 +- crates/bevy_infinite_grid/src/render/mod.rs | 16 ++++++++-------- crates/bevy_localization/Cargo.toml | 2 +- crates/bevy_pane_layout/Cargo.toml | 2 +- crates/bevy_pane_layout/src/handlers.rs | 2 +- crates/bevy_pane_layout/src/lib.rs | 2 +- crates/bevy_pane_layout/src/ui.rs | 6 +++--- crates/bevy_proto_bsn/Cargo.toml | 2 +- crates/bevy_proto_bsn/src/ast/Cargo.toml | 2 +- crates/bevy_proto_bsn/src/ast/src/lib.rs | 5 ++--- crates/bevy_proto_bsn/src/bsn_asset.rs | 2 +- crates/bevy_proto_bsn/src/bsn_reflect.rs | 4 ++-- crates/bevy_proto_bsn/src/lib.rs | 2 +- crates/bevy_proto_bsn/src/macros/Cargo.toml | 2 +- crates/bevy_proto_bsn/src/macros/src/bsn.rs | 5 ++--- .../src/macros/src/derive_construct.rs | 4 ++-- crates/bevy_transform_gizmos/Cargo.toml | 2 +- crates/bevy_undo/Cargo.toml | 2 +- templates/blank_project/Cargo.toml | 2 +- templates/getting_started/Cargo.toml | 3 +-- 80 files changed, 119 insertions(+), 135 deletions(-) diff --git a/bevy_editor_panes/bevy_2d_viewport/Cargo.toml b/bevy_editor_panes/bevy_2d_viewport/Cargo.toml index 479e3791..4b5b57f4 100644 --- a/bevy_editor_panes/bevy_2d_viewport/Cargo.toml +++ b/bevy_editor_panes/bevy_2d_viewport/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_2d_viewport" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml index 102a1a54..bf29eefb 100644 --- a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml +++ b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_3d_viewport" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index c0202737..d2155b0a 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -2,9 +2,9 @@ use bevy::{ asset::uuid::Uuid, picking::{ + PickingSystems, input::{mouse_pick_events, touch_pick_events}, pointer::{Location, PointerId, PointerInput}, - PickingSystems, }, prelude::*, render::{ @@ -18,7 +18,7 @@ use bevy_editor_cam::prelude::{DefaultEditorCamPlugins, EditorCam}; use bevy_editor_styles::Theme; use bevy_infinite_grid::{InfiniteGrid, InfiniteGridPlugin, InfiniteGridSettings}; use bevy_pane_layout::prelude::*; -use view_gizmo::{spawn_view_gizmo_target_texture, ViewGizmoPlugin}; +use view_gizmo::{ViewGizmoPlugin, spawn_view_gizmo_target_texture}; use crate::selection_box::SelectionBoxPlugin; diff --git a/bevy_editor_panes/bevy_asset_browser/Cargo.toml b/bevy_editor_panes/bevy_asset_browser/Cargo.toml index f6d0c58f..872a3dfe 100644 --- a/bevy_editor_panes/bevy_asset_browser/Cargo.toml +++ b/bevy_editor_panes/bevy_asset_browser/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_asset_browser" version = "0.1.0" -edition = "2021" +edition = "2024" [features] diff --git a/bevy_editor_panes/bevy_asset_browser/src/io/task.rs b/bevy_editor_panes/bevy_asset_browser/src/io/task.rs index c5daa48a..ba7b8910 100644 --- a/bevy_editor_panes/bevy_asset_browser/src/io/task.rs +++ b/bevy_editor_panes/bevy_asset_browser/src/io/task.rs @@ -2,7 +2,7 @@ use crate::{AssetBrowserLocation, DirectoryContent, Entry}; use bevy::{ asset::io::AssetSourceBuilders, prelude::*, - tasks::{block_on, futures_lite::StreamExt, poll_once, IoTaskPool, Task}, + tasks::{IoTaskPool, Task, block_on, futures_lite::StreamExt, poll_once}, }; #[derive(Component)] diff --git a/bevy_editor_panes/bevy_asset_browser/src/lib.rs b/bevy_editor_panes/bevy_asset_browser/src/lib.rs index e404c82c..80f4a393 100644 --- a/bevy_editor_panes/bevy_asset_browser/src/lib.rs +++ b/bevy_editor_panes/bevy_asset_browser/src/lib.rs @@ -5,9 +5,8 @@ use std::path::PathBuf; use bevy::{ asset::{ - embedded_asset, - io::{file::FileAssetReader, AssetSourceId}, - AssetPlugin, + AssetPlugin, embedded_asset, + io::{AssetSourceId, file::FileAssetReader}, }, prelude::*, }; diff --git a/bevy_editor_panes/bevy_asset_browser/src/ui/directory_content.rs b/bevy_editor_panes/bevy_asset_browser/src/ui/directory_content.rs index fba648e0..e9edc11f 100644 --- a/bevy_editor_panes/bevy_asset_browser/src/ui/directory_content.rs +++ b/bevy_editor_panes/bevy_asset_browser/src/ui/directory_content.rs @@ -1,9 +1,9 @@ use bevy::{asset::io::AssetSourceId, prelude::*}; use bevy_context_menu::{ContextMenu, ContextMenuOption}; use bevy_editor_styles::Theme; -use bevy_scroll_box::{spawn_scroll_box, ScrollBox, ScrollBoxContent}; +use bevy_scroll_box::{ScrollBox, ScrollBoxContent, spawn_scroll_box}; -use crate::{io, AssetBrowserLocation, DefaultSourceFilePath, DirectoryContent, Entry}; +use crate::{AssetBrowserLocation, DefaultSourceFilePath, DirectoryContent, Entry, io}; use crate::ui::nodes::{spawn_file_node, spawn_folder_node, spawn_source_node}; diff --git a/bevy_editor_panes/bevy_asset_browser/src/ui/nodes.rs b/bevy_editor_panes/bevy_asset_browser/src/ui/nodes.rs index 7d3ed385..235c5a1c 100644 --- a/bevy_editor_panes/bevy_asset_browser/src/ui/nodes.rs +++ b/bevy_editor_panes/bevy_asset_browser/src/ui/nodes.rs @@ -10,11 +10,11 @@ use bevy::{ use bevy_context_menu::{ContextMenu, ContextMenuOption}; use bevy_editor_styles::Theme; -use crate::{io, ui::source_id_to_string, AssetBrowserLocation}; +use crate::{AssetBrowserLocation, io, ui::source_id_to_string}; use super::{ - directory_content::{delete_file, delete_folder}, DEFAULT_SOURCE_ID_NAME, + directory_content::{delete_file, delete_folder}, }; pub(crate) fn spawn_source_node<'a>( diff --git a/bevy_editor_panes/bevy_asset_browser/src/ui/top_bar.rs b/bevy_editor_panes/bevy_asset_browser/src/ui/top_bar.rs index ca619458..f63b18dc 100644 --- a/bevy_editor_panes/bevy_asset_browser/src/ui/top_bar.rs +++ b/bevy_editor_panes/bevy_asset_browser/src/ui/top_bar.rs @@ -1,7 +1,7 @@ use bevy::{prelude::*, window::SystemCursorIcon, winit::cursor::CursorIcon}; use bevy_editor_styles::Theme; -use crate::{io, AssetBrowserLocation}; +use crate::{AssetBrowserLocation, io}; use super::source_id_to_string; diff --git a/bevy_editor_panes/bevy_marketplace_viewer/Cargo.toml b/bevy_editor_panes/bevy_marketplace_viewer/Cargo.toml index 21572b72..2629d359 100644 --- a/bevy_editor_panes/bevy_marketplace_viewer/Cargo.toml +++ b/bevy_editor_panes/bevy_marketplace_viewer/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "bevy_marketplace_viewer" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/bevy_editor_panes/bevy_preferences/Cargo.toml b/bevy_editor_panes/bevy_preferences/Cargo.toml index d776a035..504811c4 100644 --- a/bevy_editor_panes/bevy_preferences/Cargo.toml +++ b/bevy_editor_panes/bevy_preferences/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_preferences" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_editor_panes/bevy_properties_pane/Cargo.toml b/bevy_editor_panes/bevy_properties_pane/Cargo.toml index 53ef79f3..de45a1de 100644 --- a/bevy_editor_panes/bevy_properties_pane/Cargo.toml +++ b/bevy_editor_panes/bevy_properties_pane/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_properties_pane" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_editor_panes/bevy_properties_pane/src/lib.rs b/bevy_editor_panes/bevy_properties_pane/src/lib.rs index 77c41470..ee735d71 100644 --- a/bevy_editor_panes/bevy_properties_pane/src/lib.rs +++ b/bevy_editor_panes/bevy_properties_pane/src/lib.rs @@ -4,7 +4,7 @@ use bevy::{color::palettes::tailwind, prelude::*, reflect::*}; use bevy_editor_core::SelectedEntity; -use bevy_i_cant_believe_its_not_bsn::{template, Template, TemplateEntityCommandsExt}; +use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, template}; use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure}; /// Plugin for the editor properties pane. diff --git a/bevy_editor_panes/bevy_scene_tree/Cargo.toml b/bevy_editor_panes/bevy_scene_tree/Cargo.toml index 39629d3b..a817cc48 100644 --- a/bevy_editor_panes/bevy_scene_tree/Cargo.toml +++ b/bevy_editor_panes/bevy_scene_tree/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_scene_tree" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_editor_panes/bevy_scene_tree/src/lib.rs b/bevy_editor_panes/bevy_scene_tree/src/lib.rs index 3128f21f..f471de4d 100644 --- a/bevy_editor_panes/bevy_scene_tree/src/lib.rs +++ b/bevy_editor_panes/bevy_scene_tree/src/lib.rs @@ -2,7 +2,7 @@ use bevy::{app::Plugin, color::palettes::tailwind, prelude::*}; use bevy_editor_core::SelectedEntity; -use bevy_i_cant_believe_its_not_bsn::{on, template, Template, TemplateEntityCommandsExt}; +use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, on, template}; use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure}; /// Plugin for the editor scene tree pane. diff --git a/bevy_widgets/bevy_color_picker/Cargo.toml b/bevy_widgets/bevy_color_picker/Cargo.toml index af2fce70..3e68fb9d 100644 --- a/bevy_widgets/bevy_color_picker/Cargo.toml +++ b/bevy_widgets/bevy_color_picker/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_color_picker" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_command_palette/Cargo.toml b/bevy_widgets/bevy_command_palette/Cargo.toml index ff5905f2..8c4e4837 100644 --- a/bevy_widgets/bevy_command_palette/Cargo.toml +++ b/bevy_widgets/bevy_command_palette/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_command_palette" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_context_menu/Cargo.toml b/bevy_widgets/bevy_context_menu/Cargo.toml index e55a17c1..2b47e352 100644 --- a/bevy_widgets/bevy_context_menu/Cargo.toml +++ b/bevy_widgets/bevy_context_menu/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_context_menu" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_field_forms/Cargo.toml b/bevy_widgets/bevy_field_forms/Cargo.toml index 6b72fdf6..ae7ec4cc 100644 --- a/bevy_widgets/bevy_field_forms/Cargo.toml +++ b/bevy_widgets/bevy_field_forms/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_field_forms" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_field_forms/examples/nickname.rs b/bevy_widgets/bevy_field_forms/examples/nickname.rs index db909541..0a47d9f7 100644 --- a/bevy_widgets/bevy_field_forms/examples/nickname.rs +++ b/bevy_widgets/bevy_field_forms/examples/nickname.rs @@ -2,9 +2,9 @@ use bevy::{input_focus::tab_navigation::TabGroup, platform::collections::HashSet, prelude::*}; use bevy_field_forms::{ + FieldFormsPlugin, input_field::{InputField, InputFieldPlugin, Validable, ValidationChanged, ValidationState}, validate_highlight::SimpleBorderHighlight, - FieldFormsPlugin, }; fn main() { @@ -80,7 +80,9 @@ impl Validable for CharacterName { .chars() .filter(|c| !allowed_chars.contains(c)) .collect(); - Err(format!("Invalid character name. The following characters are not allowed: '{invalid_chars}'. Only letters, numbers, and underscores can be used.")) + Err(format!( + "Invalid character name. The following characters are not allowed: '{invalid_chars}'. Only letters, numbers, and underscores can be used." + )) } } } diff --git a/bevy_widgets/bevy_field_forms/examples/numeric_fields.rs b/bevy_widgets/bevy_field_forms/examples/numeric_fields.rs index 25900f33..7c2977d4 100644 --- a/bevy_widgets/bevy_field_forms/examples/numeric_fields.rs +++ b/bevy_widgets/bevy_field_forms/examples/numeric_fields.rs @@ -2,10 +2,10 @@ use bevy::{input_focus::tab_navigation::TabGroup, prelude::*}; use bevy_field_forms::{ + FieldFormsPlugin, drag_input::{DragInput, Draggable}, input_field::{InputField, Validable}, validate_highlight::SimpleBorderHighlight, - FieldFormsPlugin, }; fn main() { diff --git a/bevy_widgets/bevy_field_forms/src/text_event_mirror.rs b/bevy_widgets/bevy_field_forms/src/text_event_mirror.rs index 3f264aa2..310c8113 100644 --- a/bevy_widgets/bevy_field_forms/src/text_event_mirror.rs +++ b/bevy_widgets/bevy_field_forms/src/text_event_mirror.rs @@ -2,7 +2,7 @@ //! This is useful to create controlled text widgets with filters on top of this text widget. use bevy::prelude::*; -use bevy_text_editing::{child_traversal::FirstChildTraversal, SetText, TextChanged}; +use bevy_text_editing::{SetText, TextChanged, child_traversal::FirstChildTraversal}; /// Plugin for the text event mirror. pub struct TextEventMirrorPlugin; diff --git a/bevy_widgets/bevy_footer_bar/Cargo.toml b/bevy_widgets/bevy_footer_bar/Cargo.toml index de71c5a5..3c427888 100644 --- a/bevy_widgets/bevy_footer_bar/Cargo.toml +++ b/bevy_widgets/bevy_footer_bar/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_footer_bar" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_i-cant-believe-its-not-bsn/Cargo.toml b/bevy_widgets/bevy_i-cant-believe-its-not-bsn/Cargo.toml index 58aa3a5c..8a3724b9 100644 --- a/bevy_widgets/bevy_i-cant-believe-its-not-bsn/Cargo.toml +++ b/bevy_widgets/bevy_i-cant-believe-its-not-bsn/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_i-cant-believe-its-not-bsn" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_menu_bar/Cargo.toml b/bevy_widgets/bevy_menu_bar/Cargo.toml index 1816ffcf..e2f2de62 100644 --- a/bevy_widgets/bevy_menu_bar/Cargo.toml +++ b/bevy_widgets/bevy_menu_bar/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_menu_bar" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_scroll_box/Cargo.toml b/bevy_widgets/bevy_scroll_box/Cargo.toml index 47102bfa..9d9f33a4 100644 --- a/bevy_widgets/bevy_scroll_box/Cargo.toml +++ b/bevy_widgets/bevy_scroll_box/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_scroll_box" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true @@ -9,4 +9,3 @@ bevy_editor_styles.workspace = true [lints] workspace = true - diff --git a/bevy_widgets/bevy_text_editing/Cargo.toml b/bevy_widgets/bevy_text_editing/Cargo.toml index aa7b524d..866ee380 100644 --- a/bevy_widgets/bevy_text_editing/Cargo.toml +++ b/bevy_widgets/bevy_text_editing/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_text_editing" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_text_editing/src/editable_text_line.rs b/bevy_widgets/bevy_text_editing/src/editable_text_line.rs index 0f867ed5..fca947c5 100644 --- a/bevy_widgets/bevy_text_editing/src/editable_text_line.rs +++ b/bevy_widgets/bevy_text_editing/src/editable_text_line.rs @@ -5,8 +5,8 @@ mod render; use bevy::{ input_focus::{ - tab_navigation::{TabIndex, TabNavigationPlugin}, InputDispatchPlugin, + tab_navigation::{TabIndex, TabNavigationPlugin}, }, platform::collections::HashSet, prelude::*, @@ -14,8 +14,8 @@ use bevy::{ use bevy_clipboard::ClipboardPlugin; use crate::{ + CharPosition, HasFocus, SetCursorPosition, SetText, TEXT_SELECTION_COLOR, TextChanged, cursor::{Cursor, CursorPlugin}, - CharPosition, HasFocus, SetCursorPosition, SetText, TextChanged, TEXT_SELECTION_COLOR, }; use input::*; @@ -182,7 +182,7 @@ impl EditableTextLine { return None; } - if range.0 .0 > self.text.chars().count() || range.1 .0 > self.text.chars().count() { + if range.0.0 > self.text.chars().count() || range.1.0 > self.text.chars().count() { return None; } diff --git a/bevy_widgets/bevy_text_editing/src/text_change.rs b/bevy_widgets/bevy_text_editing/src/text_change.rs index 30d973e4..050e0bfc 100644 --- a/bevy_widgets/bevy_text_editing/src/text_change.rs +++ b/bevy_widgets/bevy_text_editing/src/text_change.rs @@ -7,7 +7,7 @@ //! The main struct in this module is `TextChange`, which encapsulates a single text change operation, //! including the range of text to be modified and the new text to be inserted. -use crate::{get_byte_position, CharPosition}; +use crate::{CharPosition, get_byte_position}; /// Represents a single text change operation. /// Any text change can be represented as a series of these operations. diff --git a/bevy_widgets/bevy_toolbar/Cargo.toml b/bevy_widgets/bevy_toolbar/Cargo.toml index 753eab01..8f4b480f 100644 --- a/bevy_widgets/bevy_toolbar/Cargo.toml +++ b/bevy_widgets/bevy_toolbar/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_toolbar" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/bevy_widgets/bevy_tooltips/Cargo.toml b/bevy_widgets/bevy_tooltips/Cargo.toml index 3eccb675..8b3cd1ce 100644 --- a/bevy_widgets/bevy_tooltips/Cargo.toml +++ b/bevy_widgets/bevy_tooltips/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_tooltips" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/crates/bevy_asset_preview/Cargo.toml b/crates/bevy_asset_preview/Cargo.toml index c2b3ef6f..03d0e3fc 100644 --- a/crates/bevy_asset_preview/Cargo.toml +++ b/crates/bevy_asset_preview/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_asset_preview" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] -bevy.workspace = true \ No newline at end of file +bevy.workspace = true diff --git a/crates/bevy_clipboard/Cargo.toml b/crates/bevy_clipboard/Cargo.toml index c17eaa7e..678b9c52 100644 --- a/crates/bevy_clipboard/Cargo.toml +++ b/crates/bevy_clipboard/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_clipboard" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] arboard = "3.4.1" diff --git a/crates/bevy_editor/Cargo.toml b/crates/bevy_editor/Cargo.toml index b4f863e6..de83d588 100644 --- a/crates/bevy_editor/Cargo.toml +++ b/crates/bevy_editor/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/crates/bevy_editor/src/load_gltf.rs b/crates/bevy_editor/src/load_gltf.rs index ee5d97e2..dcb8656a 100644 --- a/crates/bevy_editor/src/load_gltf.rs +++ b/crates/bevy_editor/src/load_gltf.rs @@ -2,7 +2,7 @@ use bevy::{ prelude::*, - tasks::{block_on, futures_lite::future, AsyncComputeTaskPool, Task}, + tasks::{AsyncComputeTaskPool, Task, block_on, futures_lite::future}, }; use rfd::{AsyncFileDialog, FileHandle}; diff --git a/crates/bevy_editor/src/project/mod.rs b/crates/bevy_editor/src/project/mod.rs index c63d6da7..8945248b 100644 --- a/crates/bevy_editor/src/project/mod.rs +++ b/crates/bevy_editor/src/project/mod.rs @@ -3,7 +3,7 @@ use bevy::log::{error, info, warn}; use serde::{Deserialize, Serialize}; use std::{path::PathBuf, time::SystemTime}; -use templates::{copy_template, Templates}; +use templates::{Templates, copy_template}; mod cache; pub mod templates; diff --git a/crates/bevy_editor_cam/Cargo.toml b/crates/bevy_editor_cam/Cargo.toml index 5af0b71e..2512aac6 100644 --- a/crates/bevy_editor_cam/Cargo.toml +++ b/crates/bevy_editor_cam/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor_cam" version = "0.1.0" -edition = "2021" +edition = "2024" description = "A camera controller for editors and CAD." license = "MIT OR Apache-2.0" keywords = ["controller", "camera", "bevy", "CAD"] diff --git a/crates/bevy_editor_cam/examples/map.rs b/crates/bevy_editor_cam/examples/map.rs index 3e21855b..c2e5e127 100644 --- a/crates/bevy_editor_cam/examples/map.rs +++ b/crates/bevy_editor_cam/examples/map.rs @@ -68,18 +68,19 @@ fn spawn_buildings( let w = half_width as isize; for x in -w..=w { for z in -w..=w { - let x = x as f32 + rng.gen::() - 0.5; - let z = z as f32 + rng.gen::() - 0.5; - let y = rng.gen::() * rng.gen::() * rng.gen::() * rng.gen::(); + let x = x as f32 + rng.r#gen::() - 0.5; + let z = z as f32 + rng.r#gen::() - 0.5; + let y = + rng.r#gen::() * rng.r#gen::() * rng.r#gen::() * rng.r#gen::(); let y_scale = 1.02f32.powf(100.0 * y); commands.spawn(( Mesh3d(mesh.clone()), MeshMaterial3d(material[rng.gen_range(0..material.len())].clone()), Transform::from_xyz(x, y_scale / 2.0 - 5.0, z).with_scale(Vec3::new( - (rng.gen::() + 0.5) * 0.3, + (rng.r#gen::() + 0.5) * 0.3, y_scale, - (rng.gen::() + 0.5) * 0.3, + (rng.r#gen::() + 0.5) * 0.3, )), )); } diff --git a/crates/bevy_editor_cam/src/controller/component.rs b/crates/bevy_editor_cam/src/controller/component.rs index 336fe1d8..7bd74fac 100644 --- a/crates/bevy_editor_cam/src/controller/component.rs +++ b/crates/bevy_editor_cam/src/controller/component.rs @@ -7,7 +7,7 @@ use std::{ use bevy::ecs::prelude::*; use bevy::log::prelude::*; -use bevy::math::{prelude::*, DMat4, DQuat, DVec2, DVec3}; +use bevy::math::{DMat4, DQuat, DVec2, DVec3, prelude::*}; use bevy::platform::time::Instant; use bevy::reflect::prelude::*; use bevy::render::prelude::*; @@ -248,11 +248,11 @@ impl EditorCam { { match motion_inputs { MotionInputs::OrbitZoom { - screenspace_inputs: ref mut movement, + screenspace_inputs: movement, .. } => movement.process_input(screenspace_input, self.smoothing.orbit), MotionInputs::PanZoom { - screenspace_inputs: ref mut movement, + screenspace_inputs: movement, .. } => movement.process_input(screenspace_input, self.smoothing.pan), MotionInputs::Zoom { .. } => (), // When in zoom-only, we ignore pan and zoom @@ -323,9 +323,7 @@ impl EditorCam { ) { let (anchor, orbit, pan, zoom) = match &mut self.current_motion { CurrentMotion::Stationary => return, - CurrentMotion::Momentum { - ref mut velocity, .. - } => { + CurrentMotion::Momentum { velocity, .. } => { velocity.decay(self.momentum, delta_time); match velocity { Velocity::None => { @@ -440,7 +438,7 @@ impl EditorCam { // Scale this with the perspective FOV, so zoom speed feels the same regardless. anchor.normalize() * zoom_amount / perspective.fov as f64 } - Projection::Orthographic(ref mut ortho) => { + Projection::Orthographic(ortho) => { // Constants are hand tuned to feel equivalent between perspective and ortho. Might // be a better way to do this correctly, if it matters. ortho.scale *= 1.0 - zoom_bounded as f32 * 0.0015; diff --git a/crates/bevy_editor_cam/src/controller/inputs.rs b/crates/bevy_editor_cam/src/controller/inputs.rs index 32642fa4..269e4ca9 100644 --- a/crates/bevy_editor_cam/src/controller/inputs.rs +++ b/crates/bevy_editor_cam/src/controller/inputs.rs @@ -2,7 +2,7 @@ use std::time::Duration; -use bevy::math::{prelude::*, DVec2}; +use bevy::math::{DVec2, prelude::*}; use bevy::reflect::prelude::*; use super::smoothing::InputQueue; @@ -109,11 +109,7 @@ impl MotionInputs { /// Motion-conserving smoothed zoom input velocity. pub fn smooth_zoom_velocity(&self) -> f64 { let velocity = self.zoom_inputs().latest_smoothed().unwrap_or(0.0) as f64; - if !velocity.is_finite() { - 0.0 - } else { - velocity - } + if !velocity.is_finite() { 0.0 } else { velocity } } /// Get a reference to the queue of zoom inputs. @@ -145,10 +141,6 @@ impl MotionInputs { let velocity = zoom_inputs.approx_smoothed(window, |v| { *v = v.abs(); }) as f64; - if !velocity.is_finite() { - 0.0 - } else { - velocity - } + if !velocity.is_finite() { 0.0 } else { velocity } } } diff --git a/crates/bevy_editor_cam/src/controller/momentum.rs b/crates/bevy_editor_cam/src/controller/momentum.rs index edd702a5..00ab3fe0 100644 --- a/crates/bevy_editor_cam/src/controller/momentum.rs +++ b/crates/bevy_editor_cam/src/controller/momentum.rs @@ -87,15 +87,11 @@ impl Velocity { pub fn decay(&mut self, momentum: Momentum, delta_time: Duration) { let is_none = match self { Velocity::None => true, - Velocity::Orbit { - ref mut velocity, .. - } => { + Velocity::Orbit { velocity, .. } => { *velocity = momentum.decay_velocity_orbit(*velocity, delta_time); velocity.length() <= Self::DECAY_THRESHOLD } - Velocity::Pan { - ref mut velocity, .. - } => { + Velocity::Pan { velocity, .. } => { *velocity = momentum.decay_velocity_pan(*velocity, delta_time); velocity.length() <= Self::DECAY_THRESHOLD } diff --git a/crates/bevy_editor_cam/src/extensions/dolly_zoom.rs b/crates/bevy_editor_cam/src/extensions/dolly_zoom.rs index 29f04dfa..35b21930 100644 --- a/crates/bevy_editor_cam/src/extensions/dolly_zoom.rs +++ b/crates/bevy_editor_cam/src/extensions/dolly_zoom.rs @@ -15,7 +15,7 @@ use bevy::render::{camera::ScalingMode, prelude::*}; use bevy::transform::prelude::*; use bevy::window::RequestRedraw; -use crate::prelude::{motion::CurrentMotion, EditorCam, EnabledMotion}; +use crate::prelude::{EditorCam, EnabledMotion, motion::CurrentMotion}; /// See the [module](self) docs. pub struct DollyZoomPlugin; diff --git a/crates/bevy_editor_cam/src/extensions/independent_skybox.rs b/crates/bevy_editor_cam/src/extensions/independent_skybox.rs index dce77b02..907219b0 100644 --- a/crates/bevy_editor_cam/src/extensions/independent_skybox.rs +++ b/crates/bevy_editor_cam/src/extensions/independent_skybox.rs @@ -7,7 +7,7 @@ use bevy::app::prelude::*; use bevy::asset::Handle; -use bevy::core_pipeline::{prelude::*, Skybox}; +use bevy::core_pipeline::{Skybox, prelude::*}; use bevy::ecs::prelude::*; use bevy::image::Image; use bevy::reflect::prelude::*; diff --git a/crates/bevy_editor_cam/src/extensions/look_to.rs b/crates/bevy_editor_cam/src/extensions/look_to.rs index 28242aa6..abc68378 100644 --- a/crates/bevy_editor_cam/src/extensions/look_to.rs +++ b/crates/bevy_editor_cam/src/extensions/look_to.rs @@ -5,7 +5,7 @@ use std::{f32::consts::PI, time::Duration}; use bevy::app::prelude::*; use bevy::ecs::prelude::*; -use bevy::math::{prelude::*, DQuat, DVec3}; +use bevy::math::{DQuat, DVec3, prelude::*}; use bevy::platform::collections::HashMap; use bevy::platform::time::Instant; use bevy::reflect::prelude::*; diff --git a/crates/bevy_editor_cam/src/input.rs b/crates/bevy_editor_cam/src/input.rs index e42c5ade..4185ab2f 100644 --- a/crates/bevy_editor_cam/src/input.rs +++ b/crates/bevy_editor_cam/src/input.rs @@ -4,7 +4,7 @@ use bevy::input::{ mouse::{MouseScrollUnit, MouseWheel}, prelude::*, }; -use bevy::math::{prelude::*, DVec2, DVec3}; +use bevy::math::{DVec2, DVec3, prelude::*}; use bevy::platform::collections::HashMap; use bevy::reflect::prelude::*; use bevy::render::prelude::*; @@ -245,7 +245,7 @@ impl EditorCamInputEvent { controller.end_move(); if let Some(pointer) = camera_map .iter() - .find(|(.., &camera)| camera == event.camera()) + .find(|&(.., &camera)| camera == event.camera()) .map(|(&pointer, ..)| pointer) { camera_map.remove(&pointer); diff --git a/crates/bevy_editor_cam/src/lib.rs b/crates/bevy_editor_cam/src/lib.rs index a50a883f..61c0df18 100644 --- a/crates/bevy_editor_cam/src/lib.rs +++ b/crates/bevy_editor_cam/src/lib.rs @@ -183,12 +183,12 @@ pub mod input; /// Common imports. pub mod prelude { pub use crate::{ - controller::{component::*, *}, DefaultEditorCamPlugins, + controller::{component::*, *}, }; } -use bevy::app::{prelude::*, PluginGroupBuilder}; +use bevy::app::{PluginGroupBuilder, prelude::*}; /// Adds [`bevy_editor_cam`](crate) functionality with all extensions and the default input plugin. pub struct DefaultEditorCamPlugins; diff --git a/crates/bevy_editor_camera/Cargo.toml b/crates/bevy_editor_camera/Cargo.toml index dab21f63..00f46bee 100644 --- a/crates/bevy_editor_camera/Cargo.toml +++ b/crates/bevy_editor_camera/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor_camera" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true 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 d4365005..5278df01 100644 --- a/crates/bevy_editor_camera/src/editor_camera_2d/mod.rs +++ b/crates/bevy_editor_camera/src/editor_camera_2d/mod.rs @@ -165,7 +165,7 @@ fn camera_zoom( continue; } - let Projection::Orthographic(ref mut projection) = projection.as_mut() else { + let Projection::Orthographic(projection) = projection.as_mut() else { panic!("EditorCamera2d requires an Orthographic projection"); }; diff --git a/crates/bevy_editor_core/Cargo.toml b/crates/bevy_editor_core/Cargo.toml index 46e20b75..baf9bb32 100644 --- a/crates/bevy_editor_core/Cargo.toml +++ b/crates/bevy_editor_core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor_core" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/crates/bevy_editor_launcher/Cargo.toml b/crates/bevy_editor_launcher/Cargo.toml index 34f81166..190cccab 100644 --- a/crates/bevy_editor_launcher/Cargo.toml +++ b/crates/bevy_editor_launcher/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor_launcher" version = "0.1.0" -edition = "2021" +edition = "2024" [lints] workspace = true diff --git a/crates/bevy_editor_launcher/src/main.rs b/crates/bevy_editor_launcher/src/main.rs index aecdffd7..a798fec1 100644 --- a/crates/bevy_editor_launcher/src/main.rs +++ b/crates/bevy_editor_launcher/src/main.rs @@ -7,11 +7,11 @@ use std::path::PathBuf; use bevy::{ ecs::schedule::common_conditions::any_with_component, prelude::*, - tasks::{block_on, futures_lite::future, IoTaskPool, Task}, + tasks::{IoTaskPool, Task, block_on, futures_lite::future}, }; use bevy_editor::project::{ - create_new_project, get_local_projects, set_project_list, templates::Templates, ProjectInfo, + ProjectInfo, create_new_project, get_local_projects, set_project_list, templates::Templates, }; use bevy_editor_styles::{StylesPlugin, Theme}; use bevy_footer_bar::{FooterBarPlugin, FooterBarSet}; diff --git a/crates/bevy_editor_launcher/src/ui.rs b/crates/bevy_editor_launcher/src/ui.rs index 18379073..a62d0135 100644 --- a/crates/bevy_editor_launcher/src/ui.rs +++ b/crates/bevy_editor_launcher/src/ui.rs @@ -2,7 +2,7 @@ use std::io::ErrorKind; use std::path::Path; use bevy::{prelude::*, ui::RelativeCursorPosition}; -use bevy_editor::project::{run_project, set_project_list, templates::Templates, ProjectInfo}; +use bevy_editor::project::{ProjectInfo, run_project, set_project_list, templates::Templates}; use bevy_editor_styles::Theme; use bevy_footer_bar::FooterBarNode; diff --git a/crates/bevy_editor_settings/Cargo.toml b/crates/bevy_editor_settings/Cargo.toml index 78a0230f..9b6a3256 100644 --- a/crates/bevy_editor_settings/Cargo.toml +++ b/crates/bevy_editor_settings/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor_settings" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/crates/bevy_editor_settings/src/file_system/de/list.rs b/crates/bevy_editor_settings/src/file_system/de/list.rs index 8b852bb6..c1d14da5 100644 --- a/crates/bevy_editor_settings/src/file_system/de/list.rs +++ b/crates/bevy_editor_settings/src/file_system/de/list.rs @@ -1,6 +1,6 @@ use bevy::{ prelude::warn, - reflect::{attributes::CustomAttributes, List, ListInfo}, + reflect::{List, ListInfo, attributes::CustomAttributes}, }; use crate::MergeStrategy; diff --git a/crates/bevy_editor_settings/src/file_system/de/mod.rs b/crates/bevy_editor_settings/src/file_system/de/mod.rs index 84c27e45..dd50893f 100644 --- a/crates/bevy_editor_settings/src/file_system/de/mod.rs +++ b/crates/bevy_editor_settings/src/file_system/de/mod.rs @@ -14,7 +14,7 @@ mod value; use array::LoadArray; use bevy::{ prelude::*, - reflect::{attributes::CustomAttributes, ReflectFromPtr, ReflectMut, TypeInfo}, + reflect::{ReflectFromPtr, ReflectMut, TypeInfo, attributes::CustomAttributes}, }; use enums::LoadEnum; use heck::ToSnakeCase; diff --git a/crates/bevy_editor_settings/src/file_system/de/structs.rs b/crates/bevy_editor_settings/src/file_system/de/structs.rs index 0b7295db..f16bce45 100644 --- a/crates/bevy_editor_settings/src/file_system/de/structs.rs +++ b/crates/bevy_editor_settings/src/file_system/de/structs.rs @@ -1,6 +1,6 @@ use bevy::reflect::Struct; -use super::{struct_utils::StructLikeInfo, LoadStructure}; +use super::{LoadStructure, struct_utils::StructLikeInfo}; pub struct LoadStruct<'a> { pub struct_info: &'a dyn StructLikeInfo, diff --git a/crates/bevy_editor_settings/src/file_system/de/tuple.rs b/crates/bevy_editor_settings/src/file_system/de/tuple.rs index 13b0ca32..0f636a1c 100644 --- a/crates/bevy_editor_settings/src/file_system/de/tuple.rs +++ b/crates/bevy_editor_settings/src/file_system/de/tuple.rs @@ -1,6 +1,6 @@ use bevy::reflect::Tuple; -use super::{tuple_utils::TupleLikeInfo, LoadStructure}; +use super::{LoadStructure, tuple_utils::TupleLikeInfo}; pub struct LoadTuple<'a> { pub tuple_info: &'a dyn TupleLikeInfo, diff --git a/crates/bevy_editor_settings/src/file_system/de/tuple_struct.rs b/crates/bevy_editor_settings/src/file_system/de/tuple_struct.rs index 17200f90..bbec1ce2 100644 --- a/crates/bevy_editor_settings/src/file_system/de/tuple_struct.rs +++ b/crates/bevy_editor_settings/src/file_system/de/tuple_struct.rs @@ -1,6 +1,6 @@ use bevy::reflect::TupleStruct; -use super::{tuple_utils::TupleLikeInfo, LoadStructure}; +use super::{LoadStructure, tuple_utils::TupleLikeInfo}; pub struct LoadTupleStruct<'a> { pub tuple_struct_info: &'a dyn TupleLikeInfo, diff --git a/crates/bevy_editor_styles/Cargo.toml b/crates/bevy_editor_styles/Cargo.toml index 29be26af..42cd5845 100644 --- a/crates/bevy_editor_styles/Cargo.toml +++ b/crates/bevy_editor_styles/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_editor_styles" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/crates/bevy_infinite_grid/Cargo.toml b/crates/bevy_infinite_grid/Cargo.toml index d0444a17..fb31fc3e 100644 --- a/crates/bevy_infinite_grid/Cargo.toml +++ b/crates/bevy_infinite_grid/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_infinite_grid" version = "0.1.0" -edition = "2021" +edition = "2024" authors = ["Nile "] [dependencies] diff --git a/crates/bevy_infinite_grid/src/lib.rs b/crates/bevy_infinite_grid/src/lib.rs index 6ae474b2..29aed89d 100644 --- a/crates/bevy_infinite_grid/src/lib.rs +++ b/crates/bevy_infinite_grid/src/lib.rs @@ -3,7 +3,7 @@ mod render; use bevy::render::view::{ - add_visibility_class, NoFrustumCulling, VisibilityClass, VisibleEntities, + NoFrustumCulling, VisibilityClass, VisibleEntities, add_visibility_class, }; use bevy::{prelude::*, render::sync_world::SyncToRenderWorld}; diff --git a/crates/bevy_infinite_grid/src/render/mod.rs b/crates/bevy_infinite_grid/src/render/mod.rs index 1cef9303..d8ef3bef 100755 --- a/crates/bevy_infinite_grid/src/render/mod.rs +++ b/crates/bevy_infinite_grid/src/render/mod.rs @@ -6,8 +6,8 @@ use bevy::{ ecs::{ query::ROQueryItem, system::{ - lifetimeless::{Read, SRes}, SystemParamItem, + lifetimeless::{Read, SRes}, }, }, image::BevyDefault, @@ -15,23 +15,23 @@ use bevy::{ pbr::MeshPipelineKey, prelude::*, render::{ + Extract, ExtractSchedule, Render, RenderApp, RenderSystems, mesh::PrimitiveTopology, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult, SetItemPipeline, ViewSortedRenderPhases, }, render_resource::{ - binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, - BindGroupLayoutEntries, BlendState, ColorTargetState, ColorWrites, CompareFunction, - DepthBiasState, DepthStencilState, DynamicUniformBuffer, FragmentState, - MultisampleState, PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, - ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, - StencilFaceState, StencilState, TextureFormat, VertexState, + BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, BlendState, + ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, + DynamicUniformBuffer, FragmentState, MultisampleState, PipelineCache, PolygonMode, + PrimitiveState, RenderPipelineDescriptor, ShaderStages, ShaderType, + SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState, + TextureFormat, VertexState, binding_types::uniform_buffer, }, renderer::{RenderDevice, RenderQueue}, sync_world::RenderEntity, view::{ExtractedView, RenderVisibleEntities, ViewTarget, VisibleEntities}, - Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }, }; diff --git a/crates/bevy_localization/Cargo.toml b/crates/bevy_localization/Cargo.toml index d87bba17..7cfb1a87 100644 --- a/crates/bevy_localization/Cargo.toml +++ b/crates/bevy_localization/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_localization" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/crates/bevy_pane_layout/Cargo.toml b/crates/bevy_pane_layout/Cargo.toml index 900de045..a1583206 100644 --- a/crates/bevy_pane_layout/Cargo.toml +++ b/crates/bevy_pane_layout/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_pane_layout" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/crates/bevy_pane_layout/src/handlers.rs b/crates/bevy_pane_layout/src/handlers.rs index d205469e..1c526c38 100644 --- a/crates/bevy_pane_layout/src/handlers.rs +++ b/crates/bevy_pane_layout/src/handlers.rs @@ -2,8 +2,8 @@ use bevy::prelude::*; use bevy_editor_styles::Theme; use crate::{ - ui::{spawn_divider, spawn_pane, spawn_resize_handle}, Divider, PaneRootNode, RootPaneLayoutNode, Size, + ui::{spawn_divider, spawn_pane, spawn_resize_handle}, }; pub(crate) fn remove_pane( diff --git a/crates/bevy_pane_layout/src/lib.rs b/crates/bevy_pane_layout/src/lib.rs index 54db3f2d..7ac5b198 100644 --- a/crates/bevy_pane_layout/src/lib.rs +++ b/crates/bevy_pane_layout/src/lib.rs @@ -29,8 +29,8 @@ use crate::{ /// Crate prelude. pub mod prelude { pub use crate::{ - registry::{PaneAppExt, PaneStructure}, PaneAreaNode, PaneContentNode, PaneHeaderNode, + registry::{PaneAppExt, PaneStructure}, }; } diff --git a/crates/bevy_pane_layout/src/ui.rs b/crates/bevy_pane_layout/src/ui.rs index 9411ee7d..1296cffb 100644 --- a/crates/bevy_pane_layout/src/ui.rs +++ b/crates/bevy_pane_layout/src/ui.rs @@ -1,10 +1,10 @@ use bevy::{prelude::*, window::SystemCursorIcon, winit::cursor::CursorIcon}; use bevy_context_menu::{ContextMenu, ContextMenuOption}; -use bevy_editor_styles::{icons, Theme}; +use bevy_editor_styles::{Theme, icons}; use crate::{ - handlers::*, registry::PaneStructure, Divider, DragState, PaneAreaNode, PaneContentNode, - PaneHeaderNode, PaneRootNode, ResizeHandle, Size, + Divider, DragState, PaneAreaNode, PaneContentNode, PaneHeaderNode, PaneRootNode, ResizeHandle, + Size, handlers::*, registry::PaneStructure, }; pub(crate) fn spawn_pane<'a>( diff --git a/crates/bevy_proto_bsn/Cargo.toml b/crates/bevy_proto_bsn/Cargo.toml index 2cdd66ea..d0b33039 100644 --- a/crates/bevy_proto_bsn/Cargo.toml +++ b/crates/bevy_proto_bsn/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_proto_bsn" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy_proto_bsn_ast = { path = "src/ast", version = "0.1.0" } diff --git a/crates/bevy_proto_bsn/src/ast/Cargo.toml b/crates/bevy_proto_bsn/src/ast/Cargo.toml index 8bcb22e4..cb34ca98 100644 --- a/crates/bevy_proto_bsn/src/ast/Cargo.toml +++ b/crates/bevy_proto_bsn/src/ast/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_proto_bsn_ast" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy.workspace = true diff --git a/crates/bevy_proto_bsn/src/ast/src/lib.rs b/crates/bevy_proto_bsn/src/ast/src/lib.rs index 6e806008..e2abf090 100644 --- a/crates/bevy_proto_bsn/src/ast/src/lib.rs +++ b/crates/bevy_proto_bsn/src/ast/src/lib.rs @@ -1,11 +1,10 @@ //! Shared BSN AST core used by both macro and assets. use syn::{ - braced, bracketed, parenthesized, - parse::{discouraged::Speculative, Parse, ParseStream}, + Block, Expr, Ident, Member, Path, Result, Token, braced, bracketed, parenthesized, + parse::{Parse, ParseStream, discouraged::Speculative}, punctuated::Punctuated, token::{self, Brace, Paren}, - Block, Expr, Ident, Member, Path, Result, Token, }; pub use quote; diff --git a/crates/bevy_proto_bsn/src/bsn_asset.rs b/crates/bevy_proto_bsn/src/bsn_asset.rs index b8152d9e..3daefa0f 100644 --- a/crates/bevy_proto_bsn/src/bsn_asset.rs +++ b/crates/bevy_proto_bsn/src/bsn_asset.rs @@ -1,5 +1,5 @@ use bevy::{ - asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}, + asset::{AssetLoader, AsyncReadExt, LoadContext, io::Reader}, prelude::*, }; use thiserror::Error; diff --git a/crates/bevy_proto_bsn/src/bsn_reflect.rs b/crates/bevy_proto_bsn/src/bsn_reflect.rs index 565859e4..56ce8972 100644 --- a/crates/bevy_proto_bsn/src/bsn_reflect.rs +++ b/crates/bevy_proto_bsn/src/bsn_reflect.rs @@ -2,7 +2,7 @@ use core::{any::TypeId, cell::RefCell, hash::BuildHasher, ops::Deref, str::FromS use bevy::{ app::App, - asset::{io::Reader, Asset, AssetLoader, AssetServer, Handle, LoadContext}, + asset::{Asset, AssetLoader, AssetServer, Handle, LoadContext, io::Reader}, ecs::{ reflect::AppTypeRegistry, world::{FromWorld, World}, @@ -61,7 +61,7 @@ impl ReflectedBsn { let key = match &bsn.key { Some(BsnKey::Static(key)) => Some(key.clone()), Some(BsnKey::Dynamic(key)) => { - return Err(ReflectError::DynamicKeyNotSupported(key.clone())) + return Err(ReflectError::DynamicKeyNotSupported(key.clone())); } None => None, }; diff --git a/crates/bevy_proto_bsn/src/lib.rs b/crates/bevy_proto_bsn/src/lib.rs index 01510678..d6a73c82 100644 --- a/crates/bevy_proto_bsn/src/lib.rs +++ b/crates/bevy_proto_bsn/src/lib.rs @@ -33,8 +33,8 @@ pub use patch::*; pub use prefab::*; pub use retain::*; -pub use bevy_proto_bsn_macros::pbsn; pub use bevy_proto_bsn_macros::Construct; +pub use bevy_proto_bsn_macros::pbsn; /// Adds support for BSN assets and reflection-based dynamic scenes. pub struct BsnPlugin; diff --git a/crates/bevy_proto_bsn/src/macros/Cargo.toml b/crates/bevy_proto_bsn/src/macros/Cargo.toml index 2f3c53e9..52aef5ea 100644 --- a/crates/bevy_proto_bsn/src/macros/Cargo.toml +++ b/crates/bevy_proto_bsn/src/macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_proto_bsn_macros" version = "0.1.0" -edition = "2021" +edition = "2024" [lib] proc-macro = true diff --git a/crates/bevy_proto_bsn/src/macros/src/bsn.rs b/crates/bevy_proto_bsn/src/macros/src/bsn.rs index 49f232c6..40d372d1 100644 --- a/crates/bevy_proto_bsn/src/macros/src/bsn.rs +++ b/crates/bevy_proto_bsn/src/macros/src/bsn.rs @@ -1,9 +1,8 @@ use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{ToTokens, format_ident, quote}; use syn::{ - parse2, + Path, parse2, punctuated::{Pair, Punctuated}, - Path, }; use bevy_proto_bsn_ast::*; diff --git a/crates/bevy_proto_bsn/src/macros/src/derive_construct.rs b/crates/bevy_proto_bsn/src/macros/src/derive_construct.rs index 09afa178..5091900c 100644 --- a/crates/bevy_proto_bsn/src/macros/src/derive_construct.rs +++ b/crates/bevy_proto_bsn/src/macros/src/derive_construct.rs @@ -1,7 +1,7 @@ -use bevy_macro_utils::{fq_std::*, BevyManifest}; +use bevy_macro_utils::{BevyManifest, fq_std::*}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse2, Data, DeriveInput, Fields, Index, Path}; +use syn::{Data, DeriveInput, Fields, Index, Path, parse2}; pub fn derive_construct(item: TokenStream) -> TokenStream { match parse2::(item) { diff --git a/crates/bevy_transform_gizmos/Cargo.toml b/crates/bevy_transform_gizmos/Cargo.toml index bd69c07f..b23cbe7b 100644 --- a/crates/bevy_transform_gizmos/Cargo.toml +++ b/crates/bevy_transform_gizmos/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_transform_gizmos" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] diff --git a/crates/bevy_undo/Cargo.toml b/crates/bevy_undo/Cargo.toml index bfa8a811..34ac0d41 100644 --- a/crates/bevy_undo/Cargo.toml +++ b/crates/bevy_undo/Cargo.toml @@ -2,7 +2,7 @@ name = "bevy_undo" description = "Subcrate for the editor crate. Contains undo functionality." version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/templates/blank_project/Cargo.toml b/templates/blank_project/Cargo.toml index 89cb687a..67496ace 100644 --- a/templates/blank_project/Cargo.toml +++ b/templates/blank_project/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "blank_project" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy_editor = { git = "https://github.com/bevyengine/bevy_editor_prototypes.git" } diff --git a/templates/getting_started/Cargo.toml b/templates/getting_started/Cargo.toml index 19ee2c4d..176ce22e 100644 --- a/templates/getting_started/Cargo.toml +++ b/templates/getting_started/Cargo.toml @@ -1,10 +1,9 @@ [package] name = "getting_started" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] bevy_editor = { git = "https://github.com/bevyengine/bevy_editor_prototypes.git" } rand = "0.8.5" rand_chacha = "0.3.1" - From b2023bdc1f2f54b76c08c90e97ecd2074b3895a2 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 18 Jul 2025 16:37:31 +0200 Subject: [PATCH 07/30] Allow loading gltf/glb files from outside the assets directory (#231) --- crates/bevy_editor/src/lib.rs | 6 +++++- crates/bevy_editor/src/load_gltf.rs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/bevy_editor/src/lib.rs b/crates/bevy_editor/src/lib.rs index 3b247b4a..59bfdcfe 100644 --- a/crates/bevy_editor/src/lib.rs +++ b/crates/bevy_editor/src/lib.rs @@ -12,6 +12,7 @@ //! - Finally, it will be a standalone application that communicates with a running Bevy game via the Bevy Remote Protocol. use bevy::app::App as BevyApp; +use bevy::asset::UnapprovedPathMode; use bevy::prelude::*; // Re-export Bevy for project use pub use bevy; @@ -36,7 +37,10 @@ pub struct RuntimePlugin; impl Plugin for RuntimePlugin { fn build(&self, bevy_app: &mut BevyApp) { - bevy_app.add_plugins(DefaultPlugins); + bevy_app.add_plugins(DefaultPlugins.set(AssetPlugin { + unapproved_path_mode: UnapprovedPathMode::Deny, + ..default() + })); } } diff --git a/crates/bevy_editor/src/load_gltf.rs b/crates/bevy_editor/src/load_gltf.rs index dcb8656a..963201c7 100644 --- a/crates/bevy_editor/src/load_gltf.rs +++ b/crates/bevy_editor/src/load_gltf.rs @@ -23,7 +23,7 @@ fn file_dropped( for event in event_reader.read() { if let FileDragAndDrop::DroppedFile { path_buf, .. } = event { let asset_path = GltfAssetLabel::Scene(0).from_asset(path_buf.clone()); - commands.spawn(SceneRoot(asset_server.load(asset_path))); + commands.spawn(SceneRoot(asset_server.load_override(asset_path))); } } } @@ -68,6 +68,6 @@ fn poll_pick_gltf( if let Some(file) = result { let path = file.path().to_owned(); let asset_path = GltfAssetLabel::Scene(0).from_asset(path); - commands.spawn(SceneRoot(asset_server.load(asset_path))); + commands.spawn(SceneRoot(asset_server.load_override(asset_path))); } } From 4c1c545c600851b11841e6b280ec2db1762b3e93 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 18 Jul 2025 20:52:14 +0200 Subject: [PATCH 08/30] Improve selection (#230) * Improve selection * Update crates/bevy_editor_core/src/selection.rs Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com> --------- Co-authored-by: JMS55 <47158642+JMS55@users.noreply.github.com> --- .../bevy_3d_viewport/src/selection_box.rs | 2 +- .../bevy_properties_pane/src/lib.rs | 2 +- bevy_editor_panes/bevy_scene_tree/src/lib.rs | 16 +--- crates/bevy_editor/src/lib.rs | 15 ++-- crates/bevy_editor_core/src/lib.rs | 31 ++----- crates/bevy_editor_core/src/selection.rs | 86 +++++++++++++++++++ crates/bevy_editor_core/src/utils.rs | 67 +++++++++++++++ 7 files changed, 177 insertions(+), 42 deletions(-) create mode 100644 crates/bevy_editor_core/src/selection.rs create mode 100644 crates/bevy_editor_core/src/utils.rs diff --git a/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs b/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs index c56fe45e..dea404b6 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs @@ -1,6 +1,6 @@ use bevy::ecs::system::SystemParam; use bevy::prelude::*; -use bevy_editor_core::SelectedEntity; +use bevy_editor_core::selection::SelectedEntity; use bevy_render::primitives::Aabb; #[derive(SystemParam)] diff --git a/bevy_editor_panes/bevy_properties_pane/src/lib.rs b/bevy_editor_panes/bevy_properties_pane/src/lib.rs index ee735d71..969dc6ae 100644 --- a/bevy_editor_panes/bevy_properties_pane/src/lib.rs +++ b/bevy_editor_panes/bevy_properties_pane/src/lib.rs @@ -3,7 +3,7 @@ //! Data can be viewed and modified in real-time, with changes being reflected in the application. use bevy::{color::palettes::tailwind, prelude::*, reflect::*}; -use bevy_editor_core::SelectedEntity; +use bevy_editor_core::selection::SelectedEntity; use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, template}; use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure}; diff --git a/bevy_editor_panes/bevy_scene_tree/src/lib.rs b/bevy_editor_panes/bevy_scene_tree/src/lib.rs index f471de4d..05067dec 100644 --- a/bevy_editor_panes/bevy_scene_tree/src/lib.rs +++ b/bevy_editor_panes/bevy_scene_tree/src/lib.rs @@ -1,7 +1,9 @@ //! An interactive, collapsible tree view for hierarchical ECS data in Bevy. use bevy::{app::Plugin, color::palettes::tailwind, prelude::*}; -use bevy_editor_core::SelectedEntity; +use bevy_editor_core::selection::{ + SelectedEntity, common_handlers::toggle_select_on_click_for_entity, +}; use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, on, template}; use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure}; @@ -62,16 +64,6 @@ fn scene_tree_row_for_entity( name: &Name, selected_entity: &SelectedEntity, ) -> Template { - let set_selected_entity_on_click = - move |mut trigger: On>, mut selected_entity: ResMut| { - if selected_entity.0 == Some(entity) { - selected_entity.0 = None; - } else { - selected_entity.0 = Some(entity); - } - trigger.propagate(false); - }; - template! { {entity}: ( Node { @@ -82,7 +74,7 @@ fn scene_tree_row_for_entity( BorderRadius::all(Val::Px(4.0)), BackgroundColor(if selected_entity.0 == Some(entity) { tailwind::NEUTRAL_700.into() } else { Color::NONE }), ) => [ - on(set_selected_entity_on_click); + on(toggle_select_on_click_for_entity(entity)); ( Text(name.into()), TextFont::from_font_size(11.0), diff --git a/crates/bevy_editor/src/lib.rs b/crates/bevy_editor/src/lib.rs index 59bfdcfe..4ead4df7 100644 --- a/crates/bevy_editor/src/lib.rs +++ b/crates/bevy_editor/src/lib.rs @@ -19,6 +19,7 @@ pub use bevy; use bevy_context_menu::ContextMenuPlugin; use bevy_editor_core::EditorCorePlugin; +use bevy_editor_core::selection::common_handlers::toggle_select_on_click; use bevy_editor_styles::StylesPlugin; // Panes @@ -112,12 +113,14 @@ fn dummy_setup( Name::new("Circle"), )); - commands.spawn(( - Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.5)))), - MeshMaterial3d(materials_3d.add(Color::WHITE)), - Name::new("Plane"), - Pickable::default(), - )); + commands + .spawn(( + Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.5)))), + MeshMaterial3d(materials_3d.add(Color::WHITE)), + Name::new("Plane"), + Pickable::default(), + )) + .observe(toggle_select_on_click); commands.spawn(( DirectionalLight { diff --git a/crates/bevy_editor_core/src/lib.rs b/crates/bevy_editor_core/src/lib.rs index 3030d86a..f1b870ee 100644 --- a/crates/bevy_editor_core/src/lib.rs +++ b/crates/bevy_editor_core/src/lib.rs @@ -1,31 +1,18 @@ //! This crate provides core functionality for the Bevy Engine Editor. -use bevy::{ecs::entity::Entities, prelude::*}; +pub mod selection; +pub mod utils; -/// Plugin for the editor scene tree pane. +use bevy::prelude::*; + +use crate::{selection::SelectionPlugin, utils::CoreUtilsPlugin}; + +/// Core plugin for the editor. +#[derive(Default)] pub struct EditorCorePlugin; impl Plugin for EditorCorePlugin { fn build(&self, app: &mut App) { - app.init_resource::() - .register_type::() - .add_systems(PostUpdate, reset_selected_entity_if_entity_despawned); - } -} - -/// The currently selected entity in the scene. -#[derive(Resource, Default, Reflect)] -#[reflect(Resource, Default)] -pub struct SelectedEntity(pub Option); - -/// System to reset [`SelectedEntity`] when the entity is despawned. -pub fn reset_selected_entity_if_entity_despawned( - mut selected_entity: ResMut, - entities: &Entities, -) { - if let Some(e) = selected_entity.0 { - if !entities.contains(e) { - selected_entity.0 = None; - } + app.add_plugins((SelectionPlugin, CoreUtilsPlugin)); } } diff --git a/crates/bevy_editor_core/src/selection.rs b/crates/bevy_editor_core/src/selection.rs new file mode 100644 index 00000000..f3c7da09 --- /dev/null +++ b/crates/bevy_editor_core/src/selection.rs @@ -0,0 +1,86 @@ +//! Editor selection module. + +use bevy::{ecs::entity::Entities, prelude::*}; + +/// Editor selection plugin. +#[derive(Default)] +pub struct SelectionPlugin; + +impl Plugin for SelectionPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .register_type::() + .add_systems(PostUpdate, reset_selected_entity_if_entity_despawned); + } +} + +/// The currently selected entity in the scene. +#[derive(Resource, Default, Reflect)] +#[reflect(Resource, Default)] +pub struct SelectedEntity(pub Option); + +impl SelectedEntity { + /// Toggle selection for an entity. + pub fn toggle(&mut self, entity: Entity) { + debug_assert_ne!(entity, Entity::PLACEHOLDER); + if self.0 == Some(entity) { + self.0 = None; + } else { + self.0 = Some(entity); + } + } + + /// Set an entity as selected. + pub fn set(&mut self, entity: Entity) { + debug_assert_ne!(entity, Entity::PLACEHOLDER); + self.0 = Some(entity); + } + + /// Empty the selection. + pub fn reset(&mut self) { + self.0 = None; + } +} + +/// System to reset [`SelectedEntity`] when the entity is despawned. +pub fn reset_selected_entity_if_entity_despawned( + mut selected_entity: ResMut, + entities: &Entities, +) { + if let Some(e) = selected_entity.0 { + if !entities.contains(e) { + selected_entity.reset(); + } + } +} + +/// Common handler observer systems for entity selection behavior. +pub mod common_handlers { + use crate::utils::DragCancelClick; + + use super::*; + + /// Toggles selection for this entity when it is clicked. + pub fn toggle_select_on_click( + mut trigger: On>, + mut selected_entity: ResMut, + ) { + if trigger.button == PointerButton::Primary { + selected_entity.toggle(trigger.target()); + trigger.propagate(false); + } + } + + /// Toggles selection for an entity when this entity is clicked. + pub fn toggle_select_on_click_for_entity( + entity: Entity, + ) -> impl FnMut(On>, ResMut) { + move |mut trigger: On>, + mut selected_entity: ResMut| { + if trigger.button == PointerButton::Primary { + selected_entity.toggle(entity); + trigger.propagate(false); + } + } + } +} diff --git a/crates/bevy_editor_core/src/utils.rs b/crates/bevy_editor_core/src/utils.rs new file mode 100644 index 00000000..d6078166 --- /dev/null +++ b/crates/bevy_editor_core/src/utils.rs @@ -0,0 +1,67 @@ +//! Editor core utils. + +use std::time::Duration; + +use bevy::{ + picking::backend::HitData, platform::collections::HashMap, platform::time::Instant, prelude::*, +}; + +/// Editor core utils plugin. +#[derive(Default)] +pub struct CoreUtilsPlugin; + +impl Plugin for CoreUtilsPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .add_event::>() + .register_type::>() + .add_observer(on_press) + .add_observer(on_drag_start) + .add_observer(on_release); + } +} + +fn on_press(trigger: On>, mut state: ResMut) { + state.0.insert(trigger.target(), Instant::now()); +} + +fn on_drag_start(trigger: On>, mut state: ResMut) { + state.0.remove(&trigger.target()); +} + +fn on_release( + trigger: On>, + mut state: ResMut, + mut commands: Commands, +) { + let now = Instant::now(); + if let Some(instant) = state.remove(&trigger.target()) { + let event = Pointer::new( + trigger.pointer_id, + trigger.pointer_location.clone(), + DragCancelClick { + button: trigger.button, + hit: trigger.hit.clone(), + duration: now - instant, + }, + ); + commands.trigger_targets(event.clone(), trigger.target()); + commands.send_event(event); + } +} + +/// Fires when a pointer sends a pointer pressed event followed by a pointer released event, with the same +/// `target` entity for both events and without a drag start event in between. +#[derive(Clone, PartialEq, Debug, Reflect)] +#[reflect(Clone, PartialEq)] +pub struct DragCancelClick { + /// Pointer button pressed and lifted to trigger this event. + pub button: PointerButton, + /// Information about the picking intersection. + pub hit: HitData, + /// Duration between the pointer pressed and lifted for this click + pub duration: Duration, +} + +#[derive(Resource, Deref, DerefMut, Default)] +struct DragCancelClickState(HashMap); From 7a1f86a483284b45efc9c4c97eb763533b033aed Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 18 Jul 2025 20:54:10 +0200 Subject: [PATCH 09/30] Update .gitignore (#229) --- .gitignore | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 65a88e51..b5cb3408 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,20 @@ # Generated by Cargo # will have compiled files and executables -debug/ -target/ +target # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock +# Local cargo config overrides +.cargo/config +.cargo/config.toml + # These are backup files generated by rustfmt **/*.rs.bk # MSVC Windows builds of rustc generate these, which store debugging information *.pdb - -# Added by cargo - -/target - # mdbook generated files design-book/book From df53e5014bba24e1adf79a501f17d5f501ecd754 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 19 Jul 2025 01:13:38 +0200 Subject: [PATCH 10/30] Depend on cart's BSN branch (#232) * Update to `0294fa89db4e213ac8e186eed6966a7ea7459d1a` This is the commit right before cart's BSN PR * Depend on cart's BSN branch * argh * this time for real * Update to latest commit + enable bevy_feathers + remove unintended changes to Cargo.toml I'm keeping the `resolver = "3"` >:D --- Cargo.toml | 11 ++++--- bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 2 +- bevy_widgets/bevy_scroll_box/src/lib.rs | 32 +++++++++---------- crates/bevy_editor_cam/Cargo.toml | 2 +- .../src/extensions/independent_skybox.rs | 2 +- crates/bevy_editor_core/src/utils.rs | 2 +- crates/bevy_infinite_grid/src/render/mod.rs | 8 ++--- crates/bevy_proto_bsn/examples/bsn_edit.rs | 6 ++-- crates/bevy_proto_bsn/src/construct.rs | 11 +------ .../bevy_proto_bsn/src/construct_reflect.rs | 6 ++-- crates/bevy_undo/src/lib.rs | 27 +++++++--------- 11 files changed, 50 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bec090f2..39f5ab6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -resolver = "2" +resolver = "3" members = ["crates/*", "bevy_editor_panes/*", "bevy_widgets/*"] exclude = ["templates/"] default-members = ["crates/bevy_editor_launcher"] @@ -27,12 +27,13 @@ unsafe_op_in_unsafe_fn = "warn" unused_qualifications = "warn" [workspace.dependencies] -bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14", features = [ +bevy = { git = "https://github.com/cart/bevy.git", rev = "ab733198b8b59ec90194797f4a1d46a12043ebfc", features = [ "wayland", + "experimental_bevy_feathers", ] } -bevy_render = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14" } -bevy_derive = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14" } -bevy_macro_utils = { git = "https://github.com/bevyengine/bevy.git", rev = "a3d406dd497205253e34ace757ab0076d50eec14" } +bevy_render = { git = "https://github.com/cart/bevy.git", rev = "ab733198b8b59ec90194797f4a1d46a12043ebfc" } +bevy_derive = { git = "https://github.com/cart/bevy.git", rev = "ab733198b8b59ec90194797f4a1d46a12043ebfc" } +bevy_macro_utils = { git = "https://github.com/cart/bevy.git", rev = "ab733198b8b59ec90194797f4a1d46a12043ebfc" } thiserror = "2.0" serde = { version = "1", features = ["derive"] } tracing-test = "0.2.5" diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index d2155b0a..693e6f3c 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -118,7 +118,7 @@ fn render_target_picking_passthrough( pointer_id: pointer_id_from_entity(pane_root), }; - commands.send_event(event_copy); + commands.write_event(event_copy); } } } diff --git a/bevy_widgets/bevy_scroll_box/src/lib.rs b/bevy_widgets/bevy_scroll_box/src/lib.rs index f73d6001..8c39464a 100644 --- a/bevy_widgets/bevy_scroll_box/src/lib.rs +++ b/bevy_widgets/bevy_scroll_box/src/lib.rs @@ -202,13 +202,13 @@ fn spawn_scroll_bar<'a>( query_computed_node.get(handle_entity).unwrap().size().y / scrollbox_size }; - scrollbox.position.offset_y = + scrollbox.position.y = -((norm_cursor_pos.y - norm_handle_size / 2.0) * content_size); - scrollbox.position.offset_y = scrollbox + scrollbox.position.y = scrollbox .position - .offset_y + .y .clamp(-(content_size - (norm_handle_size * content_size)), 0.0); - content_node.top = Val::Px(scrollbox.position.offset_y); + content_node.top = Val::Px(scrollbox.position.y); } ScrollBarHandleDirection::Horizontal => { let content_size = content_computed.size().x; @@ -218,13 +218,13 @@ fn spawn_scroll_bar<'a>( query_computed_node.get(handle_entity).unwrap().size().x / scrollbox_size }; - scrollbox.position.offset_x = + scrollbox.position.x = -((norm_cursor_pos.x - norm_handle_size / 2.0) * content_size); - scrollbox.position.offset_x = scrollbox + scrollbox.position.x = scrollbox .position - .offset_x + .x .clamp(-(content_size - (norm_handle_size * content_size)), 0.0); - content_node.left = Val::Px(scrollbox.position.offset_x); + content_node.left = Val::Px(scrollbox.position.x); } } } @@ -264,12 +264,12 @@ fn on_scroll( if keys.pressed(KeyCode::ShiftLeft) || keys.pressed(KeyCode::ShiftRight) { let max_scroll = (content_sizes.x - scrollbox_sizes.x).max(0.0); - scrollbox.position.offset_x = - (scrollbox.position.offset_x + scroll_delta).clamp(-max_scroll, 0.0); + scrollbox.position.x = + (scrollbox.position.x + scroll_delta).clamp(-max_scroll, 0.0); } else { let max_scroll = (content_sizes.y - scrollbox_sizes.y).max(0.); - scrollbox.position.offset_y = - (scrollbox.position.offset_y + scroll_delta).clamp(-max_scroll, 0.0); + scrollbox.position.y = + (scrollbox.position.y + scroll_delta).clamp(-max_scroll, 0.0); } return; // We only want to scroll 1 ScrollBox @@ -287,7 +287,7 @@ fn update_scroll_box( .first() .expect("Scrollbox children 0 should be a ScrollBoxContent"); let mut content_node = query_node.get_mut(*scroll_content).unwrap(); - content_node.top = Val::Px(scrollbox.position.offset_y); + content_node.top = Val::Px(scrollbox.position.y); } if scrollbox.overflow.x == OverflowAxis::Scroll { @@ -295,7 +295,7 @@ fn update_scroll_box( .first() .expect("Scrollbox children 0 should be a ScrollBoxContent"); let mut content_node = query_node.get_mut(*scroll_content).unwrap(); - content_node.left = Val::Px(scrollbox.position.offset_x); + content_node.left = Val::Px(scrollbox.position.x); } } } @@ -329,7 +329,7 @@ fn update_scroll_bars( (100.0, 0.0) } else { let height = (scrollbox_height / content_height * 100.0).clamp(5.0, 100.0); - let pos = (-scrollbox.position.offset_y / content_height * 100.0).clamp(0.0, 100.0); + let pos = (-scrollbox.position.y / content_height * 100.0).clamp(0.0, 100.0); (height, pos) }; @@ -370,7 +370,7 @@ fn update_scroll_bars( (100.0, 0.0) } else { let width = (scrollbox_width / content_width * 100.0).clamp(5.0, 100.0); - let pos = (-scrollbox.position.offset_x / content_width * 100.0).clamp(0.0, 100.0); + let pos = (-scrollbox.position.x / content_width * 100.0).clamp(0.0, 100.0); (width, pos) }; diff --git a/crates/bevy_editor_cam/Cargo.toml b/crates/bevy_editor_cam/Cargo.toml index 2512aac6..7a2a20aa 100644 --- a/crates/bevy_editor_cam/Cargo.toml +++ b/crates/bevy_editor_cam/Cargo.toml @@ -17,7 +17,7 @@ bevy.workspace = true bevy_derive.workspace = true [dev-dependencies] -bevy = { workspace = true, features = ["jpeg", "ktx2", "zstd"] } +bevy = { workspace = true, features = ["jpeg", "ktx2", "zstd_rust"] } rand = "0.8" [lints] diff --git a/crates/bevy_editor_cam/src/extensions/independent_skybox.rs b/crates/bevy_editor_cam/src/extensions/independent_skybox.rs index 907219b0..8393bafa 100644 --- a/crates/bevy_editor_cam/src/extensions/independent_skybox.rs +++ b/crates/bevy_editor_cam/src/extensions/independent_skybox.rs @@ -68,7 +68,7 @@ impl IndependentSkybox { impl Default for IndependentSkybox { fn default() -> Self { Self { - skybox: Default::default(), + skybox: Handle::default(), brightness: 500.0, skybox_cam_order_offset: -1_000, fov: Default::default(), diff --git a/crates/bevy_editor_core/src/utils.rs b/crates/bevy_editor_core/src/utils.rs index d6078166..0b9de23e 100644 --- a/crates/bevy_editor_core/src/utils.rs +++ b/crates/bevy_editor_core/src/utils.rs @@ -46,7 +46,7 @@ fn on_release( }, ); commands.trigger_targets(event.clone(), trigger.target()); - commands.send_event(event); + commands.write_event(event); } } diff --git a/crates/bevy_infinite_grid/src/render/mod.rs b/crates/bevy_infinite_grid/src/render/mod.rs index d8ef3bef..81ee4417 100755 --- a/crates/bevy_infinite_grid/src/render/mod.rs +++ b/crates/bevy_infinite_grid/src/render/mod.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use bevy::{ - asset::{load_internal_asset, weak_handle}, + asset::{load_internal_asset, uuid_handle}, core_pipeline::{core_2d::Transparent2d, core_3d::Transparent3d}, ecs::{ query::ROQueryItem, @@ -37,7 +37,7 @@ use bevy::{ use crate::InfiniteGridSettings; -const GRID_SHADER_HANDLE: Handle = weak_handle!("7cd38dd1-d707-481e-b38c-0eccb706e629"); +const GRID_SHADER_HANDLE: Handle = uuid_handle!("7cd38dd1-d707-481e-b38c-0eccb706e629"); pub fn render_app_builder(app: &mut App) { load_internal_asset!(app, GRID_SHADER_HANDLE, "grid.wgsl", Shader::from_wgsl); @@ -531,7 +531,7 @@ impl SpecializedRenderPipeline for InfiniteGridPipeline { vertex: VertexState { shader: GRID_SHADER_HANDLE, shader_defs: vec![], - entry_point: Cow::Borrowed("vertex"), + entry_point: Some(Cow::Borrowed("vertex")), buffers: vec![], }, primitive: PrimitiveState { @@ -567,7 +567,7 @@ impl SpecializedRenderPipeline for InfiniteGridPipeline { fragment: Some(FragmentState { shader: GRID_SHADER_HANDLE, shader_defs: vec![], - entry_point: Cow::Borrowed("fragment"), + entry_point: Some(Cow::Borrowed("fragment")), targets: vec![Some(ColorTargetState { format, blend: Some(BlendState::ALPHA_BLENDING), diff --git a/crates/bevy_proto_bsn/examples/bsn_edit.rs b/crates/bevy_proto_bsn/examples/bsn_edit.rs index 784eff04..880ffe8f 100644 --- a/crates/bevy_proto_bsn/examples/bsn_edit.rs +++ b/crates/bevy_proto_bsn/examples/bsn_edit.rs @@ -5,7 +5,7 @@ use bevy::prelude::*; use bevy_proto_bsn::*; -#[derive(Resource, Default)] +#[derive(Resource)] struct EditorState { bsn: Handle, } @@ -21,7 +21,9 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugins(BsnPlugin) .register_type::() - .init_resource::() + .insert_resource(EditorState { + bsn: Handle::default(), + }) .add_systems( Startup, |mut commands: Commands, diff --git a/crates/bevy_proto_bsn/src/construct.rs b/crates/bevy_proto_bsn/src/construct.rs index 63e90c22..c84b83bf 100644 --- a/crates/bevy_proto_bsn/src/construct.rs +++ b/crates/bevy_proto_bsn/src/construct.rs @@ -2,9 +2,7 @@ use alloc::borrow::Cow; use bevy::{ ecs::{ bundle::{BundleFromComponents, DynamicBundle}, - component::{ - ComponentId, Components, ComponentsRegistrator, RequiredComponents, StorageType, - }, + component::{ComponentId, Components, ComponentsRegistrator, StorageType}, system::EntityCommands, world::error::EntityMutableFetchError, }, @@ -195,13 +193,6 @@ unsafe impl Bundle for ConstructTuple { B::component_ids(components, ids); } - fn register_required_components( - components: &mut ComponentsRegistrator, - required_components: &mut RequiredComponents, - ) { - B::register_required_components(components, required_components); - } - fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { B::get_component_ids(components, ids); } diff --git a/crates/bevy_proto_bsn/src/construct_reflect.rs b/crates/bevy_proto_bsn/src/construct_reflect.rs index f3dbab6a..9b9b241b 100644 --- a/crates/bevy_proto_bsn/src/construct_reflect.rs +++ b/crates/bevy_proto_bsn/src/construct_reflect.rs @@ -140,7 +140,7 @@ pub(crate) fn register_reflect_construct(app: &mut App) { app.register_type_data::(); app.register_type_data::(); app.register_type_data::(); - app.register_type_data::(); + app.register_type_data::(); app.register_type_data::(); app.register_type_data::(); app.register_type_data::(); @@ -240,7 +240,7 @@ pub(crate) fn register_reflect_construct(app: &mut App) { app.register_type_data::(); app.register_type_data::(); app.register_type_data::(); - app.register_type_data::(); + app.register_type_data::(); app.register_type_data::(); app.register_type_data::(); app.register_type::(); @@ -252,7 +252,7 @@ pub(crate) fn register_reflect_construct(app: &mut App) { app.register_type_data::(); app.register_type_data::(); app.register_type_data::(); - app.register_type_data::(); + app.register_type_data::(); app.register_type::(); app.register_type::(); app.register_type_data::(); diff --git a/crates/bevy_undo/src/lib.rs b/crates/bevy_undo/src/lib.rs index 08297866..a1210087 100644 --- a/crates/bevy_undo/src/lib.rs +++ b/crates/bevy_undo/src/lib.rs @@ -682,7 +682,7 @@ impl EditorChange for ReflectedComponentCh .entity_mut(e) .insert(::from_reflect(&self.old_value).unwrap()) .insert(OneFrameUndoIgnore::default()); - world.send_event(UndoRedoApplied:: { + world.write_event(UndoRedoApplied:: { entity: e, _phantom: std::marker::PhantomData, }); @@ -794,7 +794,7 @@ impl EditorChange for ReflectedAddedCompon .resource_mut::() .storage .insert(dst, OneFrameUndoIgnore::default()); - world.send_event(UndoRedoApplied:: { + world.write_event(UndoRedoApplied:: { entity: dst, _phantom: std::marker::PhantomData, }); @@ -915,7 +915,7 @@ impl EditorChange for ReflectedRemovedComp .entity_mut(dst) .insert(::from_reflect(&self.old_value).unwrap()) .insert(OneFrameUndoIgnore::default()); - world.send_event(UndoRedoApplied:: { + world.write_event(UndoRedoApplied:: { entity: dst, _phantom: std::marker::PhantomData, }); @@ -1261,11 +1261,8 @@ fn apply_for_every_typed_field( ); } } - bevy::reflect::ReflectMut::Map(s) => { - for field_idx in 0..s.len() { - let (_key, value) = s.get_at_mut(field_idx).unwrap(); - apply_for_every_typed_field(value, applyer, max_recursion - 1); - } + bevy::reflect::ReflectMut::Map(_s) => { + unimplemented!("See: https://github.com/bevyengine/bevy/pull/19802"); } bevy::reflect::ReflectMut::Enum(s) => { for field_idx in 0..s.field_len() { @@ -1535,7 +1532,7 @@ mod tests { app.update(); let test_id = app.world_mut().spawn_empty().id(); - app.world_mut().send_event(NewChange { + app.world_mut().write_event(NewChange { change: Arc::new(AddedEntity { entity: test_id }), }); @@ -1560,7 +1557,7 @@ mod tests { assert!(app.world_mut().get_entity(test_id).is_ok()); - app.world_mut().send_event(UndoRedo::Undo); + app.world_mut().write_event(UndoRedo::Undo); app.update(); app.update(); @@ -1572,7 +1569,7 @@ mod tests { assert!(app.world_mut().get::(test_id).is_none()); assert!(app.world_mut().get_entity(test_id).is_ok()); - app.world_mut().send_event(UndoRedo::Undo); + app.world_mut().write_event(UndoRedo::Undo); app.update(); app.update(); @@ -1589,10 +1586,10 @@ mod tests { let test_id_1 = app.world_mut().spawn(UndoMarker).id(); let test_id_2 = app.world_mut().spawn(UndoMarker).id(); - app.world_mut().send_event(NewChange { + app.world_mut().write_event(NewChange { change: Arc::new(AddedEntity { entity: test_id_1 }), }); - app.world_mut().send_event(NewChange { + app.world_mut().write_event(NewChange { change: Arc::new(AddedEntity { entity: test_id_2 }), }); @@ -1606,14 +1603,14 @@ mod tests { app.cleanup(); app.world_mut().entity_mut(test_id_1).despawn(); - app.world_mut().send_event(NewChange { + app.world_mut().write_event(NewChange { change: Arc::new(RemovedEntity { entity: test_id_1 }), }); app.update(); app.update(); - app.world_mut().send_event(UndoRedo::Undo); + app.world_mut().write_event(UndoRedo::Undo); app.update(); app.update(); From 4f83bfc817c18cfb5da02977dfbe428768349039 Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 20 Jul 2025 20:16:29 +0200 Subject: [PATCH 11/30] Humble Beginnings for the `ActionRegistry` (#234) --- crates/bevy_editor/src/load_gltf.rs | 56 ++++---- crates/bevy_editor_core/src/actions.rs | 174 +++++++++++++++++++++++++ crates/bevy_editor_core/src/lib.rs | 10 +- 3 files changed, 208 insertions(+), 32 deletions(-) create mode 100644 crates/bevy_editor_core/src/actions.rs diff --git a/crates/bevy_editor/src/load_gltf.rs b/crates/bevy_editor/src/load_gltf.rs index 963201c7..fe650559 100644 --- a/crates/bevy_editor/src/load_gltf.rs +++ b/crates/bevy_editor/src/load_gltf.rs @@ -4,6 +4,7 @@ use bevy::{ prelude::*, tasks::{AsyncComputeTaskPool, Task, block_on, futures_lite::future}, }; +use bevy_editor_core::prelude::*; use rfd::{AsyncFileDialog, FileHandle}; pub(crate) struct LoadGltfPlugin; @@ -11,7 +12,9 @@ pub(crate) struct LoadGltfPlugin; impl Plugin for LoadGltfPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .add_systems(Update, (pick_gltf, poll_pick_gltf, file_dropped)); + .add_systems(Update, (poll_pick_gltf, file_dropped)) + .register_action("load-gltf", "Load GLTF", pick_gltf_action) + .register_action_binding("load-gltf", [KeyCode::ControlLeft, KeyCode::KeyL]); } } @@ -21,7 +24,10 @@ fn file_dropped( mut event_reader: EventReader, ) { for event in event_reader.read() { - if let FileDragAndDrop::DroppedFile { path_buf, .. } = event { + if let FileDragAndDrop::DroppedFile { path_buf, .. } = event + && let Some(extension) = path_buf.extension() + && (extension == "gtlf" || extension == "glb") + { let asset_path = GltfAssetLabel::Scene(0).from_asset(path_buf.clone()); commands.spawn(SceneRoot(asset_server.load_override(asset_path))); } @@ -31,24 +37,18 @@ fn file_dropped( #[derive(Resource, Default)] pub(crate) struct GltfFilepickerTask(Option>>); -pub(crate) fn pick_gltf( - mut file_picker_task: ResMut, - keyboard_input: Res>, -) { +pub(crate) fn pick_gltf_action(mut file_picker_task: ResMut) { if file_picker_task.0.is_some() { return; } - - if keyboard_input.pressed(KeyCode::ControlLeft) && keyboard_input.just_pressed(KeyCode::KeyL) { - file_picker_task.0 = Some( - AsyncComputeTaskPool::get().spawn( - AsyncFileDialog::new() - .set_title("Load GLTF file") - .add_filter("gltf/glb", &["gltf", "glb"]) - .pick_file(), - ), - ); - } + file_picker_task.0 = Some( + AsyncComputeTaskPool::get().spawn( + AsyncFileDialog::new() + .set_title("Load GLTF file") + .add_filter("gltf/glb", &["gltf", "glb"]) + .pick_file(), + ), + ); } fn poll_pick_gltf( @@ -56,18 +56,14 @@ fn poll_pick_gltf( asset_server: Res, mut commands: Commands, ) { - let Some(task) = &mut file_picker_task.0 else { - return; - }; - - let Some(result) = block_on(future::poll_once(task)) else { - return; - }; - file_picker_task.0 = None; - - if let Some(file) = result { - let path = file.path().to_owned(); - let asset_path = GltfAssetLabel::Scene(0).from_asset(path); - commands.spawn(SceneRoot(asset_server.load_override(asset_path))); + if let Some(task) = &mut file_picker_task.0 + && let Some(result) = block_on(future::poll_once(task)) + { + file_picker_task.0 = None; + if let Some(file) = result { + let path = file.path().to_owned(); + let asset_path = GltfAssetLabel::Scene(0).from_asset(path); + commands.spawn(SceneRoot(asset_server.load_override(asset_path))); + } } } diff --git a/crates/bevy_editor_core/src/actions.rs b/crates/bevy_editor_core/src/actions.rs new file mode 100644 index 00000000..466861ad --- /dev/null +++ b/crates/bevy_editor_core/src/actions.rs @@ -0,0 +1,174 @@ +//! Editor actions module. + +use bevy::{ecs::system::SystemId, prelude::*}; + +/// Editor selection plugin. +#[derive(Default)] +pub struct ActionsPlugin; + +impl Plugin for ActionsPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .init_resource::() + .add_systems(Update, run_actions_on_binding); + } +} + +fn run_actions_on_binding(world: &mut World) { + world.resource_scope(|world, mut bindings: Mut| { + world.resource_scope(|world, input: Mut>| { + // Sort by the invserse amount of keys in the binding so that simpler keybinds don't prevent more complex ones from triggering. + bindings.list.sort_by_key(|(v, _)| usize::MAX - v.len()); + 'outer: for (binding, action_id) in &bindings.list { + if let Some(last_key) = binding.last() { + for key in &binding[..(binding.len() - 1)] { + if !input.pressed(*key) { + continue 'outer; + } + } + if input.just_pressed(*last_key) { + world.run_action(action_id); + break 'outer; + } + } + } + }); + }); +} + +/// The registry for [`Action`]s +#[derive(Resource, Default)] +pub struct ActionRegistry { + actions: Vec, +} + +impl ActionRegistry { + /// Register an action. + pub fn register( + &mut self, + id: impl Into, + label: impl Into, + system_id: SystemId<(), ()>, + ) { + self.actions.push(Action { + id: id.into(), + label: label.into(), + system_id, + }); + } + + /// Run an action. + pub fn run(&mut self, world: &mut World, action_id: impl Into) { + let action_id = action_id.into(); + if let Some(action) = self.actions.iter_mut().find(|ac| ac.id == action_id) + && let Err(error) = world.run_system(action.system_id) + { + error!("Failed to run action '{}': {}", action.id, error); + } + } +} + +/// List of keybindings to [`Action`]s +#[derive(Resource, Default)] +pub struct ActionBindings { + list: Vec<(Vec, String)>, +} + +impl ActionBindings { + /// Add a binding for an action. + pub fn add_binding( + &mut self, + action_id: impl Into, + binding: impl IntoIterator, + ) { + self.list + .push((binding.into_iter().collect(), action_id.into())); + } +} + +/// Defines some action with an id and a label for display. +pub struct Action { + id: String, + #[expect(dead_code)] + label: String, + system_id: SystemId, +} + +/// [`ActionRegistry`] extension trait for [`App`]. +pub trait ActionAppExt { + /// Register an action. + fn register_action( + &mut self, + id: impl Into, + label: impl Into, + system: impl IntoSystem<(), (), M> + 'static, + ) -> &mut Self; + + /// Register an action binding. + fn register_action_binding( + &mut self, + action_id: impl Into, + binding: impl IntoIterator, + ) -> &mut Self; +} + +impl ActionAppExt for App { + fn register_action( + &mut self, + id: impl Into, + label: impl Into, + system: impl IntoSystem<(), (), M> + 'static, + ) -> &mut Self { + let system_id = self.world_mut().register_system(system); + self.world_mut() + .get_resource_or_init::() + .register(id, label, system_id); + self + } + + fn register_action_binding( + &mut self, + action_id: impl Into, + binding: impl IntoIterator, + ) -> &mut Self { + self.world_mut() + .get_resource_or_init::() + .add_binding(action_id, binding); + self + } +} + +/// [`ActionRegistry`] extension trait for [`World`]. +pub trait ActionWorldExt { + /// Run an action. + fn run_action(&mut self, id: impl Into) -> &mut Self; +} + +impl ActionWorldExt for World { + fn run_action(&mut self, id: impl Into) -> &mut Self { + self.resource_scope(|world, mut registry: Mut| registry.run(world, id)); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn run_action() { + #[derive(Resource)] + struct Counter(u32); + let mut app = App::new(); + app.insert_resource(Counter(0)); + app.insert_resource(ActionRegistry::default()); + + app.register_action("action", "Action", |mut counter: ResMut| { + counter.0 += 1; + }); + + app.world_mut().run_action("action"); + + assert_eq!(app.world().resource::().0, 1); + } +} diff --git a/crates/bevy_editor_core/src/lib.rs b/crates/bevy_editor_core/src/lib.rs index f1b870ee..0fe049bd 100644 --- a/crates/bevy_editor_core/src/lib.rs +++ b/crates/bevy_editor_core/src/lib.rs @@ -1,11 +1,17 @@ //! This crate provides core functionality for the Bevy Engine Editor. +pub mod actions; pub mod selection; pub mod utils; use bevy::prelude::*; -use crate::{selection::SelectionPlugin, utils::CoreUtilsPlugin}; +use crate::{actions::ActionsPlugin, selection::SelectionPlugin, utils::CoreUtilsPlugin}; + +/// Crate prelude. +pub mod prelude { + pub use crate::actions::{ActionAppExt, ActionWorldExt}; +} /// Core plugin for the editor. #[derive(Default)] @@ -13,6 +19,6 @@ pub struct EditorCorePlugin; impl Plugin for EditorCorePlugin { fn build(&self, app: &mut App) { - app.add_plugins((SelectionPlugin, CoreUtilsPlugin)); + app.add_plugins((ActionsPlugin, SelectionPlugin, CoreUtilsPlugin)); } } From ea5cb9a5efd2b09eeb84da824009c159e13191ad Mon Sep 17 00:00:00 2001 From: Tim Date: Sun, 20 Jul 2025 20:20:54 +0200 Subject: [PATCH 12/30] Port Viewport Panes to BSN and feathers (#233) --- bevy_editor_panes/bevy_2d_viewport/src/lib.rs | 58 +++++++++--------- bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 61 +++++++++++-------- .../bevy_3d_viewport/src/view_gizmo.rs | 40 ++++++------ crates/bevy_editor/src/lib.rs | 17 ++++++ crates/bevy_pane_layout/src/components.rs | 54 ++++++++++++++++ crates/bevy_pane_layout/src/lib.rs | 8 ++- crates/bevy_pane_layout/src/ui.rs | 26 ++++---- 7 files changed, 174 insertions(+), 90 deletions(-) create mode 100644 crates/bevy_pane_layout/src/components.rs diff --git a/bevy_editor_panes/bevy_2d_viewport/src/lib.rs b/bevy_editor_panes/bevy_2d_viewport/src/lib.rs index b9efd252..815c15ee 100644 --- a/bevy_editor_panes/bevy_2d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_2d_viewport/src/lib.rs @@ -1,11 +1,13 @@ //! 2d Viewport for Bevy use bevy::{ + feathers::theme::ThemedText, prelude::*, render::{ camera::RenderTarget, render_resource::{Extent3d, TextureFormat, TextureUsages}, view::RenderLayers, }, + scene2::{CommandsSpawnScene, bsn, on}, ui::ui_layout_system, }; use bevy_editor_camera::{EditorCamera2d, EditorCamera2dPlugin}; @@ -87,20 +89,8 @@ fn on_pane_creation( let image_handle = images.add(image); - let image_id = commands - .spawn(( - ImageNode::new(image_handle.clone()), - Node { - position_type: PositionType::Absolute, - top: Val::ZERO, - bottom: Val::ZERO, - left: Val::ZERO, - right: Val::ZERO, - ..default() - }, - ChildOf(structure.content), - )) - .id(); + // Remove the existing structure + commands.entity(structure.area).despawn(); let camera_id = commands .spawn(( @@ -110,7 +100,7 @@ fn on_pane_creation( ..default() }, Camera { - target: RenderTarget::Image(image_handle.into()), + target: RenderTarget::Image(image_handle.clone().into()), clear_color: ClearColorConfig::Custom(theme.viewport.background_color), ..default() }, @@ -119,18 +109,26 @@ fn on_pane_creation( .id(); commands - .entity(image_id) - .observe( - move |_trigger: On>, mut query: Query<&mut EditorCamera2d>| { - let mut editor_camera = query.get_mut(camera_id).unwrap(); - editor_camera.enabled = true; - }, - ) - .observe( - move |_trigger: On>, mut query: Query<&mut EditorCamera2d>| { - query.get_mut(camera_id).unwrap().enabled = false; - }, - ); + .spawn_scene(bsn! { + :editor_pane [ + :editor_pane_header [ + (Text("2D Viewport") ThemedText), + ], + :editor_pane_body [ + ImageNode::new(image_handle.clone()) + :fit_to_parent + on(move |_trigger: On>, mut query: Query<&mut EditorCamera2d>| { + let mut editor_camera = query.get_mut(camera_id).unwrap(); + editor_camera.enabled = true; + }) + on(move |_trigger: On>, mut query: Query<&mut EditorCamera2d>| { + query.get_mut(camera_id).unwrap().enabled = false; + }) + ], + ] + }) + .insert(Node::default()) + .insert(ChildOf(structure.root)); commands .entity(structure.root) @@ -149,10 +147,12 @@ fn update_render_target_size( mut images: ResMut>, ) { for (pane_root, viewport) in &query { - let content_node_id = children_query + let Some(content_node_id) = children_query .iter_descendants(pane_root) .find(|e| content.contains(*e)) - .unwrap(); + else { + continue; + }; let Ok((computed_node, global_transform)) = pos_query.get(content_node_id) else { continue; diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index 693e6f3c..0fca3488 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -1,6 +1,7 @@ //! 3D Viewport for Bevy use bevy::{ asset::uuid::Uuid, + feathers::theme::ThemedText, picking::{ PickingSystems, input::{mouse_pick_events, touch_pick_events}, @@ -12,15 +13,16 @@ use bevy::{ render_resource::{Extent3d, TextureFormat, TextureUsages}, view::RenderLayers, }, + scene2::{CommandsSpawnScene, bsn, on}, ui::ui_layout_system, }; use bevy_editor_cam::prelude::{DefaultEditorCamPlugins, EditorCam}; use bevy_editor_styles::Theme; use bevy_infinite_grid::{InfiniteGrid, InfiniteGridPlugin, InfiniteGridSettings}; use bevy_pane_layout::prelude::*; -use view_gizmo::{ViewGizmoPlugin, spawn_view_gizmo_target_texture}; +use view_gizmo::ViewGizmoPlugin; -use crate::selection_box::SelectionBoxPlugin; +use crate::{selection_box::SelectionBoxPlugin, view_gizmo::view_gizmo_node}; mod selection_box; mod view_gizmo; @@ -160,28 +162,31 @@ fn on_pane_creation( let pointer_id = pointer_id_from_entity(structure.root); commands.spawn((pointer_id, ChildOf(structure.root))); + // Remove the existing structure + commands.entity(structure.area).despawn(); + + let image = image_handle.clone(); commands - .spawn(( - ImageNode::new(image_handle.clone()), - Node { - position_type: PositionType::Absolute, - top: Val::ZERO, - bottom: Val::ZERO, - left: Val::ZERO, - right: Val::ZERO, - ..default() - }, - ChildOf(structure.content), - )) - .with_children(|parent| { - spawn_view_gizmo_target_texture(images, parent); + .spawn_scene(bsn! { + :editor_pane [ + :editor_pane_header [ + (Text("3D Viewport") ThemedText), + ], + :editor_pane_body [ + ImageNode::new(image.clone()) + :fit_to_parent + on(|trigger: On>, mut commands: Commands| { + commands.entity(trigger.target()).insert(Active); + }) + on(|trigger: On>, mut commands: Commands| { + commands.entity(trigger.target()).remove::(); + }) + [ :view_gizmo_node ] + ], + ] }) - .observe(|trigger: On>, mut commands: Commands| { - commands.entity(trigger.target()).insert(Active); - }) - .observe(|trigger: On>, mut commands: Commands| { - commands.entity(trigger.target()).remove::(); - }); + .insert(Node::default()) + .insert(ChildOf(structure.root)); let camera_id = commands .spawn(( @@ -206,18 +211,20 @@ fn on_pane_creation( fn update_render_target_size( query: Query<(Entity, &Bevy3dViewport)>, mut camera_query: Query<&Camera>, - content: Query<&PaneContentNode>, + bodies: Query<&PaneContentNode>, children_query: Query<&Children>, computed_node_query: Query<&ComputedNode, Changed>, mut images: ResMut>, ) { for (pane_root, viewport) in &query { - let content_node_id = children_query + let Some(pane_body) = children_query .iter_descendants(pane_root) - .find(|e| content.contains(*e)) - .unwrap(); + .find(|e| bodies.contains(*e)) + else { + continue; + }; - let Ok(computed_node) = computed_node_query.get(content_node_id) else { + let Ok(computed_node) = computed_node_query.get(pane_body) else { continue; }; // TODO Convert to physical pixels diff --git a/bevy_editor_panes/bevy_3d_viewport/src/view_gizmo.rs b/bevy_editor_panes/bevy_3d_viewport/src/view_gizmo.rs index 0f8d3324..20cccacf 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/view_gizmo.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/view_gizmo.rs @@ -4,14 +4,16 @@ use bevy::{ asset::RenderAssetUsages, - ecs::relationship::RelatedSpawnerCommands, + ecs::template::template, prelude::*, render::{ render_resource::{Extent3d, Face, TextureDimension, TextureFormat, TextureUsages}, view::RenderLayers, }, + scene2::{Scene, bsn}, }; use bevy_editor_cam::prelude::EditorCam; +use bevy_pane_layout::components::fit_to_parent; // That value was picked arbitrarily pub const VIEW_GIZMO_TEXTURE_SIZE: u32 = 125; @@ -34,10 +36,18 @@ pub struct ViewGizmoCamera; #[derive(Component)] pub struct ViewGizmoCameraTarget(pub Handle); -pub fn spawn_view_gizmo_target_texture( - mut images: ResMut<'_, Assets>, - parent: &mut RelatedSpawnerCommands, -) { +pub fn view_gizmo_node() -> impl Scene { + bsn! { + :fit_to_parent + Node { + width: Val::Px({VIEW_GIZMO_TEXTURE_SIZE as f32}), + height: Val::Px({VIEW_GIZMO_TEXTURE_SIZE as f32}), + } + template(view_gizmo_template) + } +} + +fn view_gizmo_template(entity: &mut EntityWorldMut) -> Result<()> { let size = Extent3d { width: VIEW_GIZMO_TEXTURE_SIZE, height: VIEW_GIZMO_TEXTURE_SIZE, @@ -54,24 +64,14 @@ pub fn spawn_view_gizmo_target_texture( target_texture.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT; - let image = images.add(target_texture); + let image = entity.resource_mut::>().add(target_texture); - // TODO don't hardcode it to top left - // TODO send input events to the image target - parent.spawn(( - ImageNode::new(image.clone()), - Node { - position_type: PositionType::Absolute, - top: Val::ZERO, - bottom: Val::ZERO, - left: Val::ZERO, - right: Val::ZERO, - width: Val::Px(VIEW_GIZMO_TEXTURE_SIZE as f32), - height: Val::Px(VIEW_GIZMO_TEXTURE_SIZE as f32), - ..default() - }, + entity.insert(( ViewGizmoCameraTarget(image.clone()), + ImageNode::new(image.clone()), )); + + Ok(()) } fn setup_view_gizmo( diff --git a/crates/bevy_editor/src/lib.rs b/crates/bevy_editor/src/lib.rs index 4ead4df7..4c19c294 100644 --- a/crates/bevy_editor/src/lib.rs +++ b/crates/bevy_editor/src/lib.rs @@ -11,12 +11,20 @@ //! which transforms the user's application into an editor that runs their game. //! - Finally, it will be a standalone application that communicates with a running Bevy game via the Bevy Remote Protocol. +use std::time::Duration; + use bevy::app::App as BevyApp; use bevy::asset::UnapprovedPathMode; use bevy::prelude::*; +use bevy::{ + core_widgets::CoreWidgetsPlugins, + feathers::{FeathersPlugin, dark_theme::create_dark_theme, theme::UiTheme}, + input_focus::{InputDispatchPlugin, tab_navigation::TabNavigationPlugin}, +}; // Re-export Bevy for project use pub use bevy; +use bevy::winit::{UpdateMode, WinitSettings}; use bevy_context_menu::ContextMenuPlugin; use bevy_editor_core::EditorCorePlugin; use bevy_editor_core::selection::common_handlers::toggle_select_on_click; @@ -64,12 +72,21 @@ impl Plugin for EditorPlugin { AssetBrowserPanePlugin, LoadGltfPlugin, MeshPickingPlugin, + CoreWidgetsPlugins, + InputDispatchPlugin, + TabNavigationPlugin, + FeathersPlugin, )) .insert_resource(MeshPickingSettings { // Workaround for the Mesh2d circle blocking picking in the 3d viewport (even though it is not visible). require_markers: true, ..default() }) + .insert_resource(UiTheme(create_dark_theme())) + .insert_resource(WinitSettings { + focused_mode: UpdateMode::reactive(Duration::from_secs_f64(1.0 / 60.0)), + unfocused_mode: UpdateMode::reactive_low_power(Duration::from_secs(1)), + }) .add_systems(Startup, dummy_setup); } } diff --git a/crates/bevy_pane_layout/src/components.rs b/crates/bevy_pane_layout/src/components.rs new file mode 100644 index 00000000..163f0cb6 --- /dev/null +++ b/crates/bevy_pane_layout/src/components.rs @@ -0,0 +1,54 @@ +//! Pane UI components module. + +use bevy::{ + ecs::template::template, + feathers::containers::{pane, pane_body, pane_header}, + prelude::*, + scene2::{Scene, bsn}, +}; + +use crate::{PaneContentNode, PaneHeaderNode, ui::header_context_menu}; + +/// A standard editor pane. +pub fn editor_pane() -> impl Scene { + bsn! { + :pane + :fit_to_parent + Node { + margin: UiRect::all(Val::Px(1.)), + } + } +} + +/// A standard editor pane header. +pub fn editor_pane_header() -> impl Scene { + bsn! { + :pane_header + PaneHeaderNode + template(|_| Ok(header_context_menu())) + } +} + +/// A standard editor pane body. +pub fn editor_pane_body() -> impl Scene { + bsn! { + :pane_body + PaneContentNode + Node { + flex_grow: 1., + } + } +} + +/// Align this node's position and size with its parent. +pub fn fit_to_parent() -> impl Scene { + bsn! { + Node { + position_type: PositionType::Absolute, + top: Val::ZERO, + bottom: Val::ZERO, + left: Val::ZERO, + right: Val::ZERO, + } + } +} diff --git a/crates/bevy_pane_layout/src/lib.rs b/crates/bevy_pane_layout/src/lib.rs index 7ac5b198..9a0a132e 100644 --- a/crates/bevy_pane_layout/src/lib.rs +++ b/crates/bevy_pane_layout/src/lib.rs @@ -1,5 +1,6 @@ //! Resizable, divider-able panes for Bevy. +pub mod components; mod handlers; mod pane_drop_area; pub mod registry; @@ -30,6 +31,7 @@ use crate::{ pub mod prelude { pub use crate::{ PaneAreaNode, PaneContentNode, PaneHeaderNode, + components::*, registry::{PaneAppExt, PaneStructure}, }; } @@ -189,13 +191,13 @@ struct PaneRootNode { } /// Node to denote the area of the Pane. -#[derive(Component)] +#[derive(Component, Clone, Default)] pub struct PaneAreaNode; /// Node to add widgets into the header of a Pane. -#[derive(Component)] +#[derive(Component, Clone, Default)] pub struct PaneHeaderNode; /// Node to denote the content space of the Pane. -#[derive(Component)] +#[derive(Component, Clone, Default)] pub struct PaneContentNode; diff --git a/crates/bevy_pane_layout/src/ui.rs b/crates/bevy_pane_layout/src/ui.rs index 1296cffb..6805f47e 100644 --- a/crates/bevy_pane_layout/src/ui.rs +++ b/crates/bevy_pane_layout/src/ui.rs @@ -7,6 +7,20 @@ use crate::{ Size, handlers::*, registry::PaneStructure, }; +pub fn header_context_menu() -> ContextMenu { + ContextMenu::new([ + ContextMenuOption::new("Close", |mut commands, entity| { + commands.run_system_cached_with(remove_pane, entity); + }), + ContextMenuOption::new("Split - Horizontal", |mut commands, entity| { + commands.run_system_cached_with(split_pane, (entity, false)); + }), + ContextMenuOption::new("Split - Vertical", |mut commands, entity| { + commands.run_system_cached_with(split_pane, (entity, true)); + }), + ]) +} + pub(crate) fn spawn_pane<'a>( commands: &'a mut Commands, theme: &Theme, @@ -57,17 +71,7 @@ pub(crate) fn spawn_pane<'a>( }, theme.pane.header_background_color, theme.pane.header_border_radius, - ContextMenu::new([ - ContextMenuOption::new("Close", |mut commands, entity| { - commands.run_system_cached_with(remove_pane, entity); - }), - ContextMenuOption::new("Split - Horizontal", |mut commands, entity| { - commands.run_system_cached_with(split_pane, (entity, false)); - }), - ContextMenuOption::new("Split - Vertical", |mut commands, entity| { - commands.run_system_cached_with(split_pane, (entity, true)); - }), - ]), + header_context_menu(), PaneHeaderNode, ChildOf(area), )) From e371407f08051a6641f7109ee7f9dce7a40ec836 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 21 Jul 2025 00:40:38 +0200 Subject: [PATCH 13/30] Port Properties Pane to BSN and Feathers (#235) * Port Properties Pane to BSN and Feathers * Adjust editor core prelude --- .../bevy_properties_pane/src/lib.rs | 221 +++++++++--------- crates/bevy_editor_core/src/lib.rs | 6 +- crates/bevy_editor_core/src/utils.rs | 41 +++- crates/bevy_pane_layout/src/components.rs | 11 +- 4 files changed, 165 insertions(+), 114 deletions(-) diff --git a/bevy_editor_panes/bevy_properties_pane/src/lib.rs b/bevy_editor_panes/bevy_properties_pane/src/lib.rs index 969dc6ae..68883519 100644 --- a/bevy_editor_panes/bevy_properties_pane/src/lib.rs +++ b/bevy_editor_panes/bevy_properties_pane/src/lib.rs @@ -2,76 +2,86 @@ //! //! Data can be viewed and modified in real-time, with changes being reflected in the application. -use bevy::{color::palettes::tailwind, prelude::*, reflect::*}; -use bevy_editor_core::selection::SelectedEntity; -use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, template}; -use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure}; +use bevy::{ + feathers::theme::ThemedText, + prelude::*, + reflect::*, + scene2::{CommandsSpawnScene, Scene, SceneList, bsn}, +}; +use bevy_editor_core::prelude::*; +use bevy_pane_layout::prelude::*; /// Plugin for the editor properties pane. pub struct PropertiesPanePlugin; impl Plugin for PropertiesPanePlugin { fn build(&self, app: &mut App) { - app.register_pane("Properties", setup_pane) - .add_systems(PostUpdate, update_properties_pane); + app.register_pane("Properties", setup_pane).add_systems( + Update, + update_properties_pane.run_if( + resource_changed:: + .or(any_match_filter::>), + ), + ); } } /// Root UI node of the properties pane. -#[derive(Component)] -struct PropertiesPaneRoot; +#[derive(Component, Default, Clone)] +struct PropertiesPaneBody; fn setup_pane(pane: In, mut commands: Commands) { - commands.entity(pane.content).insert(( - PropertiesPaneRoot, - Node { - flex_direction: FlexDirection::Column, - flex_grow: 1.0, - column_gap: Val::Px(4.0), - padding: UiRect::all(Val::Px(8.0)), - ..Default::default() - }, - BackgroundColor(tailwind::NEUTRAL_600.into()), - )); + // Remove the existing structure + commands.entity(pane.area).despawn(); + + commands + .spawn_scene(bsn! { + :editor_pane [ + :editor_pane_header [ + (Text("Properties") ThemedText), + ], + :editor_pane_body + PropertiesPaneBody + ] + }) + .insert(Node::default()) + .insert(ChildOf(pane.root)); } fn update_properties_pane( - panes: Query>, + pane_bodies: Query>, selected_entity: Res, world: &World, mut commands: Commands, ) { - for pane in &panes { + for pane_body in &pane_bodies { + commands.entity(pane_body).despawn_children(); commands - .entity(pane) - .build_children(properties_pane(&selected_entity, world)); + .spawn_scene(properties_pane(&selected_entity, world)) + .insert(Node::default()) + .insert(ChildOf(pane_body)); } } -fn properties_pane(selected_entity: &SelectedEntity, world: &World) -> Template { +fn properties_pane(selected_entity: &SelectedEntity, world: &World) -> impl Scene { match selected_entity.0 { - Some(selected_entity) => component_list(selected_entity, world), - None => template! { - Node { - flex_direction: FlexDirection::Column, - ..Default::default() - } => [ - ( - Text("Select an entity to inspect".into()), - TextFont::from_font_size(14.0), - ); - ]; - - }, + Some(selected_entity) => bsn! {Node { flex_direction: FlexDirection::Column } [ + {component_list(selected_entity, world)} + ]} + .boxed_scene(), + None => bsn! { + (Text("Select an entity to inspect") ThemedText) + } + .boxed_scene(), } } -fn component_list(entity: Entity, world: &World) -> Template { +fn component_list(entity: Entity, world: &World) -> impl SceneList { let type_registry = world.resource::().read(); world .inspect_entity(entity) .unwrap() - .flat_map(|component_info| { + .map(|component_info| { let type_info = component_info .type_id() .and_then(|type_id| type_registry.get_type_info(type_id)); @@ -88,131 +98,120 @@ fn component_list(entity: Entity, world: &World) -> Template { reflect_component.reflect(entity_ref.unwrap()) }); - template! { + bsn! { Node { flex_direction: FlexDirection::Column, margin: UiRect::all(Val::Px(4.0)), - - ..Default::default() - } => [ - // Collapsible header for the component + } [ Node { flex_direction: FlexDirection::Row, align_items: AlignItems::Center, - ..Default::default() - } => [ - ( - Text(format!("⯆ {name}")), - TextFont::from_font_size(14.0), - TextColor(Color::WHITE), - ); - ]; + } [ + TextFont::from_font_size(14.0) + Text({format!("{name}")}) + TextColor(Color::WHITE) + ], // Component fields - @{ match reflect { - Some(reflect) => component(type_info, reflect), - None => template! { + ({ match reflect { + Some(reflect) => component(type_info, reflect).boxed_scene(), + None => bsn! { Node { flex_direction: FlexDirection::Row, - ..Default::default() - } => [ - ( - Text("".into()), - TextFont::from_font_size(10.0), - TextColor(Color::srgb(1.0, 0.0, 0.0)), - ); - ]; - }, - } }; - ]; + } [ + Text("") + TextFont::from_font_size(10.0) + TextColor(Color::srgb(1.0, 0.0, 0.0)) + ] + }.boxed_scene(), + }}) + ] } }) - .collect() + .collect::>() } -fn component(type_info: Option<&TypeInfo>, reflect: &dyn Reflect) -> Template { +fn component(type_info: Option<&TypeInfo>, reflect: &dyn Reflect) -> impl Scene { match type_info { - Some(TypeInfo::Struct(struct_info)) => reflected_struct(struct_info, reflect), - Some(TypeInfo::TupleStruct(tuple_struct_info)) => reflected_tuple_struct(tuple_struct_info), - Some(TypeInfo::Enum(enum_info)) => reflected_enum(enum_info), - _ => template! {}, + Some(TypeInfo::Struct(info)) => reflected_struct(info, reflect).boxed_scene(), + Some(TypeInfo::TupleStruct(info)) => reflected_tuple_struct(info).boxed_scene(), + Some(TypeInfo::Enum(info)) => reflected_enum(info).boxed_scene(), + _ => bsn! {}.boxed_scene(), } } -fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect) -> Template { +fn reflected_struct(struct_info: &StructInfo, reflect: &dyn Reflect) -> impl Scene { let fields = struct_info .iter() .enumerate() - .flat_map(|(i, field)| { - let value = reflect + .map(|(i, field)| { + let valuee = reflect .reflect_ref() .as_struct() .map(|s| s.field_at(i)) .map(|v| format!("{v:?}")) .unwrap_or("".to_string()); - template! { + let field_name = field.name(); + bsn! { Node { flex_direction: FlexDirection::Row, margin: UiRect::vertical(Val::Px(2.0)), - ..Default::default() - } => [ + } [ ( - Text(field.name().to_string()), - TextFont::from_font_size(12.0), - TextColor(Color::srgb(0.8, 0.8, 0.8)), - ); + Text(field_name) + TextFont::from_font_size(12.0) + TextColor(Color::srgb(0.8, 0.8, 0.8)) + ), ( // Value (use reflection to get value as string) - Text(value), - TextFont::from_font_size(10.0), - TextColor(Color::WHITE), - ); - ]; + Text({valuee.clone()}) + TextFont::from_font_size(10.0) + TextColor(Color::WHITE) + ), + ] } }) - .collect::