Skip to content
This repository was archived by the owner on Apr 16, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions bevy_editor_panes/bevy_3d_viewport/src/selection_box.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use bevy_editor_core::selection::SelectedEntity;
use bevy_editor_core::selection::EditorSelection;
use bevy_render::primitives::Aabb;

#[derive(SystemParam)]
Expand Down Expand Up @@ -68,7 +68,7 @@ fn draw_fallback_selection_box(gizmos: &mut Gizmos, global_transform: &GlobalTra

pub fn selection_box_system(
show: Res<ShowSelectionBox>,
selected_entity: Res<SelectedEntity>,
selection: Res<EditorSelection>,
mut gizmos: Gizmos,
queries: SelectionBoxQueries,
meshes: Res<Assets<Mesh>>,
Expand All @@ -77,25 +77,23 @@ pub fn selection_box_system(
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);
for entity in selection.iter() {
// 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);
}
}
}
}
Expand Down
17 changes: 8 additions & 9 deletions bevy_editor_panes/bevy_properties_pane/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use bevy::{
reflect::*,
scene2::{CommandsSpawnScene, Scene, SceneList, bsn},
};
use bevy_editor_core::prelude::*;
use bevy_editor_core::{prelude::*, selection::common_conditions::primary_selection_changed};
use bevy_pane_layout::prelude::*;

/// Plugin for the editor properties pane.
Expand All @@ -19,8 +19,7 @@ impl Plugin for PropertiesPanePlugin {
app.register_pane("Properties", setup_pane).add_systems(
Update,
update_properties_pane.run_if(
resource_changed::<SelectedEntity>
.or(any_match_filter::<Added<PropertiesPaneBody>>),
primary_selection_changed.or(any_match_filter::<Added<PropertiesPaneBody>>),
),
);
}
Expand Down Expand Up @@ -50,23 +49,23 @@ fn setup_pane(pane: In<PaneStructure>, mut commands: Commands) {

fn update_properties_pane(
pane_bodies: Query<Entity, With<PropertiesPaneBody>>,
selected_entity: Res<SelectedEntity>,
selection: Res<EditorSelection>,
world: &World,
mut commands: Commands,
) {
for pane_body in &pane_bodies {
commands.entity(pane_body).despawn_children();
commands
.spawn_scene(properties_pane(&selected_entity, world))
.spawn_scene(properties_pane(&selection, world))
.insert(Node::default())
.insert(ChildOf(pane_body));
}
}

fn properties_pane(selected_entity: &SelectedEntity, world: &World) -> impl Scene {
match selected_entity.0 {
Some(selected_entity) => bsn! {Node { flex_direction: FlexDirection::Column } [
{component_list(selected_entity, world)}
fn properties_pane(selection: &EditorSelection, world: &World) -> impl Scene {
match selection.primary() {
Some(selection) => bsn! {Node { flex_direction: FlexDirection::Column } [
{component_list(selection, world)}
]}
.boxed_scene(),
None => bsn! {
Expand Down
39 changes: 25 additions & 14 deletions bevy_editor_panes/bevy_scene_tree/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
//! An interactive, collapsible tree view for hierarchical ECS data in Bevy.

use bevy::{app::Plugin, color::palettes::tailwind, prelude::*};
use bevy_editor_core::selection::{
SelectedEntity, common_handlers::toggle_select_on_click_for_entity,
};
use bevy_editor_core::selection::EditorSelection;
use bevy_i_cant_believe_its_not_bsn::{Template, TemplateEntityCommandsExt, on, template};
use bevy_pane_layout::prelude::{PaneAppExt, PaneStructure};

Expand Down Expand Up @@ -36,8 +34,8 @@ fn setup_pane(pane: In<PaneStructure>, mut commands: Commands) {
BackgroundColor(tailwind::NEUTRAL_600.into()),
))
.observe(
|mut trigger: On<Pointer<Click>>, mut selected_entity: ResMut<SelectedEntity>| {
selected_entity.0 = None;
|mut trigger: On<Pointer<Click>>, mut selection: ResMut<EditorSelection>| {
selection.clear();
trigger.propagate(false);
},
);
Expand All @@ -46,24 +44,37 @@ fn setup_pane(pane: In<PaneStructure>, mut commands: Commands) {
fn update_scene_tree(
scene_trees: Query<Entity, With<SceneTreeRoot>>,
scene_entities: Query<(Entity, &Name)>,
selected_entity: Res<SelectedEntity>,
selection: Res<EditorSelection>,
mut commands: Commands,
) {
for scene_tree in &scene_trees {
let tree_rows: Template = scene_entities
.iter()
.flat_map(|(entity, name)| scene_tree_row_for_entity(entity, name, &selected_entity))
.flat_map(|(entity, name)| scene_tree_row_for_entity(entity, name, &selection))
.collect();

commands.entity(scene_tree).build_children(tree_rows);
}
}

fn scene_tree_row_for_entity(
entity: Entity,
name: &Name,
selected_entity: &SelectedEntity,
) -> Template {
fn scene_tree_row_for_entity(entity: Entity, name: &Name, selection: &EditorSelection) -> Template {
let selection_handler =
move |mut trigger: On<Pointer<Click>>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut selection: ResMut<EditorSelection>| {
if trigger.button != PointerButton::Primary {
return;
}

trigger.propagate(false);
let ctrl = keyboard_input.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
if ctrl {
selection.toggle(entity);
} else {
selection.set(entity);
}
};

template! {
{entity}: (
Node {
Expand All @@ -72,9 +83,9 @@ fn scene_tree_row_for_entity(
..Default::default()
},
BorderRadius::all(Val::Px(4.0)),
BackgroundColor(if selected_entity.0 == Some(entity) { tailwind::NEUTRAL_700.into() } else { Color::NONE }),
BackgroundColor(if selection.contains(entity) { tailwind::NEUTRAL_700.into() } else { Color::NONE }),
) => [
on(toggle_select_on_click_for_entity(entity));
on(selection_handler);
(
Text(name.into()),
TextFont::from_font_size(11.0),
Expand Down
17 changes: 8 additions & 9 deletions crates/bevy_editor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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;
use bevy_editor_core::selection::Selectable;
use bevy_editor_styles::StylesPlugin;

// Panes
Expand Down Expand Up @@ -130,14 +130,13 @@ 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(),
))
.observe(toggle_select_on_click);
commands.spawn((
Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.5)))),
MeshMaterial3d(materials_3d.add(Color::WHITE)),
Name::new("Plane"),
Pickable::default(),
Selectable,
));

commands.spawn((
DirectionalLight {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_editor_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{actions::ActionsPlugin, selection::SelectionPlugin, utils::CoreUtils
pub mod prelude {
pub use crate::{
actions::{ActionAppExt, ActionWorldExt},
selection::SelectedEntity,
selection::EditorSelection,
utils::IntoBoxedScene,
};
}
Expand Down
Loading