diff --git a/rmf_site_editor/src/site/load.rs b/rmf_site_editor/src/site/load.rs index 14996ad9..c47ff40c 100644 --- a/rmf_site_editor/src/site/load.rs +++ b/rmf_site_editor/src/site/load.rs @@ -284,6 +284,7 @@ fn generate_site_entities( .insert(Dependents(dependents)); } + // let mut model_description_dependents = HashMap::>::new(); for (scenario_id, scenario_bundle) in &site_data.scenarios { let parent = match scenario_bundle.scenario.parent_scenario.0 { Some(parent_id) => *id_to_entity.get(&parent_id).unwrap_or(&site_id), diff --git a/rmf_site_editor/src/site/mod.rs b/rmf_site_editor/src/site/mod.rs index 5fb642d4..9bcaa652 100644 --- a/rmf_site_editor/src/site/mod.rs +++ b/rmf_site_editor/src/site/mod.rs @@ -210,6 +210,7 @@ impl Plugin for SitePlugin { .add_event::() .add_event::() .add_event::() + .add_event::() .add_event::() .add_event::() .add_event::() @@ -372,9 +373,10 @@ impl Plugin for SitePlugin { add_location_visuals, add_fiducial_visuals, update_level_visibility, - update_current_scenario.before(update_scenario_properties), update_scenario_properties, - remove_instances, + handle_remove_scenarios.before(update_current_scenario), + update_current_scenario.before(update_scenario_properties), + handle_remove_instances, update_changed_lane, update_lane_for_moved_anchor, ) diff --git a/rmf_site_editor/src/site/scenario.rs b/rmf_site_editor/src/site/scenario.rs index e11322a8..4d326b7d 100644 --- a/rmf_site_editor/src/site/scenario.rs +++ b/rmf_site_editor/src/site/scenario.rs @@ -17,7 +17,10 @@ use crate::{ interaction::Selection, - site::{CurrentScenario, Delete, InstanceMarker, Pending, Pose, Scenario, SiteParent}, + site::{ + CurrentScenario, Delete, Dependents, InstanceMarker, Pending, Pose, Scenario, + ScenarioBundle, ScenarioMarker, SiteParent, + }, CurrentWorkspace, }; use bevy::prelude::*; @@ -26,6 +29,7 @@ use std::collections::HashMap; #[derive(Clone, Copy, Debug, Event)] pub struct ChangeCurrentScenario(pub Entity); +/// Handles changes to the current scenario pub fn update_current_scenario( mut commands: Commands, mut selection: ResMut, @@ -169,21 +173,91 @@ pub fn update_scenario_properties( } } +#[derive(Debug, Clone, Copy, Event)] +pub struct RemoveScenario(pub Entity); + +/// When a scenario is removed, all child scenarios are removed as well +pub fn handle_remove_scenarios( + mut commands: Commands, + mut remove_scenario_requests: EventReader, + mut change_current_scenario: EventWriter, + mut delete: EventWriter, + mut current_scenario: ResMut, + current_workspace: Res, + mut scenarios: Query< + (Entity, &Scenario, Option<&mut Dependents>), + With, + >, + children: Query<&Children>, +) { + for request in remove_scenario_requests.read() { + // Any child scenarios or instances added within the subtree are considered dependents + // to be deleted + let mut subtree_dependents = std::collections::HashSet::::new(); + let mut queue = vec![request.0]; + while let Some(scenario_entity) = queue.pop() { + if let Ok((_, scenario, _)) = scenarios.get(scenario_entity) { + scenario.added_instances.iter().for_each(|(e, _)| { + subtree_dependents.insert(*e); + }); + } + if let Ok(children) = children.get(scenario_entity) { + children.iter().for_each(|e| { + subtree_dependents.insert(*e); + queue.push(*e); + }); + } + } + + // Change to parent scenario, else root, else create an empty scenario and switch to it + if let Some(parent_scenario_entity) = scenarios + .get(request.0) + .map(|(_, s, _)| s.parent_scenario.0) + .ok() + .flatten() + { + change_current_scenario.send(ChangeCurrentScenario(parent_scenario_entity)); + } else if let Some((root_scenario_entity, _, _)) = scenarios + .iter() + .filter(|(e, s, _)| request.0 != *e && s.parent_scenario.0.is_none()) + .next() + { + change_current_scenario.send(ChangeCurrentScenario(root_scenario_entity)); + } else { + let new_scenario_entity = commands + .spawn(ScenarioBundle::::default()) + .set_parent(current_workspace.root.expect("No current site")) + .id(); + *current_scenario = CurrentScenario(Some(new_scenario_entity)); + } + + // Delete with dependents + if let Ok((_, _, Some(mut depenedents))) = scenarios.get_mut(request.0) { + depenedents.extend(subtree_dependents.iter()); + } else { + commands + .entity(request.0) + .insert(Dependents(subtree_dependents)); + } + delete.send(Delete::new(request.0).and_dependents()); + } +} + #[derive(Debug, Clone, Copy, Event)] pub struct RemoveInstance(pub Entity); /// Handle requests to remove model instances. If an instance was added in this scenario, or if /// the scenario is root, the InstanceMarker is removed, allowing it to be permanently deleted. /// Otherwise, it is only temporarily removed. -pub fn remove_instances( +pub fn handle_remove_instances( mut commands: Commands, mut scenarios: Query<&mut Scenario>, current_scenario: ResMut, mut change_current_scenario: EventWriter, - mut removals: EventReader, + mut remove_requests: EventReader, mut delete: EventWriter, ) { - for removal in removals.read() { + for removal in remove_requests.read() { let Some(current_scenario_entity) = current_scenario.0 else { delete.send(Delete::new(removal.0)); return; diff --git a/rmf_site_editor/src/widgets/creation.rs b/rmf_site_editor/src/widgets/creation.rs index 0481e9ea..a45b1251 100644 --- a/rmf_site_editor/src/widgets/creation.rs +++ b/rmf_site_editor/src/widgets/creation.rs @@ -22,7 +22,7 @@ use crate::{ widgets::{prelude::*, AssetGalleryStatus}, AppState, CurrentWorkspace, }; -use bevy::{ecs::system::SystemParam, prelude::*}; +use bevy::{audio::Source, ecs::system::SystemParam, prelude::*}; use bevy_egui::egui::{CollapsingHeader, ComboBox, Grid, Ui}; use rmf_site_format::{ @@ -236,7 +236,6 @@ impl<'w, 's> Creation<'w, 's> { _ => return, }; - ui.label("Properties"); ui.horizontal(|ui| { ui.label("Description Name"); let mut new_name = pending_model.name.clone(); @@ -266,7 +265,7 @@ impl<'w, 's> Creation<'w, 's> { pending_model.scale = new_scale; } - ui.add_space(5.0); + ui.separator(); if let Some(asset_gallery) = &mut self.asset_gallery { match self.app_state.get() { AppState::MainMenu @@ -346,15 +345,20 @@ impl<'w, 's> Creation<'w, 's> { ui.text_edit_singleline(&mut pending_model.instance_name); } }); - ui.add_space(3.0); - if ui.button("Browse fuel").clicked() { - asset_gallery.show = true; + if ui + .selectable_label(asset_gallery.show, "Browse Fuel") + .clicked() + { + asset_gallery.show = !asset_gallery.show; } } AppState::WorkcellEditor => { - if ui.button("Browse fuel").clicked() { - asset_gallery.show = true; + if ui + .selectable_label(asset_gallery.show, "Browse Fuel") + .clicked() + { + asset_gallery.show = !asset_gallery.show; } if ui.button("Spawn visual").clicked() { let workcell_model = WorkcellModel { @@ -507,13 +511,22 @@ struct PendingDrawing { pub recall_source: RecallAssetSource, } -#[derive(Clone, Default)] +#[derive(Clone)] struct PendingModelInstance { pub description_entity: Option, pub instance_name: String, } -#[derive(Clone, Default)] +impl Default for PendingModelInstance { + fn default() -> Self { + Self { + description_entity: None, + instance_name: "".to_string(), + } + } +} + +#[derive(Clone)] struct PendingModelDescription { pub name: String, pub source: AssetSource, @@ -522,3 +535,16 @@ struct PendingModelDescription { pub spawn_instance: bool, pub instance_name: String, } + +impl Default for PendingModelDescription { + fn default() -> Self { + Self { + name: "".to_string(), + source: AssetSource::default(), + recall_source: RecallAssetSource::default(), + scale: Scale::default(), + spawn_instance: true, + instance_name: " ".to_string(), + } + } +} diff --git a/rmf_site_editor/src/widgets/view_scenarios.rs b/rmf_site_editor/src/widgets/view_scenarios.rs index c393d193..d4b6d78d 100644 --- a/rmf_site_editor/src/widgets/view_scenarios.rs +++ b/rmf_site_editor/src/widgets/view_scenarios.rs @@ -18,8 +18,8 @@ use crate::{ interaction::{Select, Selection}, site::{ - Category, Change, ChangeCurrentScenario, CurrentScenario, Delete, NameInSite, Scenario, - ScenarioMarker, + Category, Change, ChangeCurrentScenario, CurrentScenario, Delete, NameInSite, + RemoveScenario, Scenario, ScenarioMarker, }, widgets::prelude::*, Icons, @@ -53,6 +53,7 @@ pub struct ViewScenarios<'w, 's> { >, change_name: EventWriter<'w, Change>, change_current_scenario: EventWriter<'w, ChangeCurrentScenario>, + remove_scenario: EventWriter<'w, RemoveScenario>, display_scenarios: ResMut<'w, ScenarioDisplay>, current_scenario: ResMut<'w, CurrentScenario>, instances: Query< @@ -95,6 +96,14 @@ impl<'w, 's> ViewScenarios<'w, 's> { self.change_name .send(Change::new(NameInSite(new_name), current_scenario_entity)); } + if ui + .button("❌") + .on_hover_text("Delete this scenario and all its child scenarios") + .clicked() + { + self.remove_scenario + .send(RemoveScenario(current_scenario_entity)); + } }); ui.label("From Previous:"); // Added