From 0c52ea6eec98515978c70d5e9d7af551663e6dbf Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Thu, 21 Mar 2024 04:17:12 -0400 Subject: [PATCH] Fix hierarchy selection issues --- studio/src/camera_controller.rs | 17 +++-- studio/src/interaction/selection.rs | 110 +++++++++++++++------------- studio/src/ui/inspector.rs | 54 +++++++------- 3 files changed, 97 insertions(+), 84 deletions(-) diff --git a/studio/src/camera_controller.rs b/studio/src/camera_controller.rs index 66d4ae4..bee5881 100644 --- a/studio/src/camera_controller.rs +++ b/studio/src/camera_controller.rs @@ -6,8 +6,8 @@ use bevy::{ }; use crate::{ - input::{Action, CameraMotion, InputAction, Lifecycle, Selection}, - interaction::Selected, + input::{self, Action, CameraMotion, InputAction, Lifecycle}, + interaction::Selection, math::{ Easing, InterpPosition, InterpVelocity, Interpolation, InterpolationSystem, NearlyEq, NearlyZero, Velocity, @@ -155,20 +155,25 @@ fn process_physical_inputs( fn process_focus_inputs( mut cmd: Commands, + r_selection: Option>, mut input: EventReader, mut q_camera: Query<(Entity, &CameraController, &mut Velocity)>, q_interp_vel: Query<&InterpVelocity>, mut q_interp_pos: Query<&mut InterpPosition>, - q_selected_xform: Query<&GlobalTransform, With>, + q_global_xforms: Query<&GlobalTransform>, ) { + let Some(selection) = r_selection else { + return; + }; + if input.read().any(|action| { matches!( action, - InputAction(Lifecycle::Stop, Action::Selection(Selection::Focus)) + InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Focus)) ) }) { - let Ok(target) = q_selected_xform - .get_single() + let Ok(target) = q_global_xforms + .get(**selection) .map(|xform| xform.translation()) else { return; diff --git a/studio/src/interaction/selection.rs b/studio/src/interaction/selection.rs index b37e881..6483d22 100644 --- a/studio/src/interaction/selection.rs +++ b/studio/src/interaction/selection.rs @@ -49,43 +49,52 @@ impl Plugin for SelectionPlugin { .run_if(resource_changed_or_removed::()), ); - app.add_systems(Update, sync_editor_selected); + app.add_systems(PostUpdate, propagate_space_editor_to_studio); } } #[derive(Component, Clone, Copy)] pub struct Selectable; -#[derive(Component, Clone, Copy)] -pub struct Selected; - #[derive(Resource, Deref, DerefMut, Debug)] pub struct Selection(Entity); -fn sync_editor_selected( +fn propagate_space_editor_to_studio( mut cmd: Commands, - q_new_selected: Query, Without)>, - q_editor_selected: Query>, - q_selected: Query>, - q_added_editor_selected: Query< - Entity, - (Without, Added), - >, + r_selection: Option>, + q_new_selected: Query>, + mut er_deselected: RemovedComponents, + q_parents: Query<&Parent>, + q_selectable: Query<&Selectable>, ) { - for ent in q_new_selected.iter() { - for ed_ent in q_editor_selected.iter() { - cmd.entity(ed_ent) - .remove::(); + let studio_selection_deselected_in_space_editor = r_selection + .as_ref() + .map(|selection| { + er_deselected + .read() + .any(|deselected| deselected == ***selection) + }) + .unwrap_or(false); + + if q_new_selected.is_empty() && studio_selection_deselected_in_space_editor { + cmd.remove_resource::(); + } else { + let mut new_selected_iter = q_new_selected.iter(); + let Some(selected) = new_selected_iter.next().and_then(|se_selected| { + nearest_selectable_parent(se_selected, &q_parents, &q_selectable) + }) else { + return; + }; + + if r_selection.is_none() || **r_selection.unwrap() != selected { + cmd.insert_resource(Selection(selected)); } - cmd.entity(ent).insert(space_editor::prelude::Selected); - } - for ent in q_added_editor_selected.iter() { - cmd.entity(ent).insert(Selected); - cmd.insert_resource(Selection(ent)); - - for ent in q_selected.iter() { - cmd.entity(ent).remove::(); + if new_selected_iter.next().is_some() { + warn!( + "Multiple new entities were selected in Space Editor, but \ + Storyteller Studio doesn't currently support multiple selections" + ); } } } @@ -117,31 +126,11 @@ fn update_selection( mut ev_input: EventReader, q_parents: Query<&Parent>, q_selectable: Query<&Selectable>, - q_selected: Query>, ) { if let Some(event) = ev_select.read().last() { - // Clear any existing `Selected` components - for ent in q_selected.iter() { - cmd.entity(ent).remove::(); - } - // Find the nearest Selectable parent of the target entity - let mut ent = event.target; - let ent_selected = loop { - if q_selectable.get(ent).is_ok() { - break Some(ent); - } - - if let Ok(parent) = q_parents.get(ent) { - ent = parent.get(); - } else { - break None; - } - }; - - if let Some(ent) = ent_selected { + if let Some(ent) = nearest_selectable_parent(event.target, &q_parents, &q_selectable) { cmd.insert_resource(Selection(ent)); - cmd.entity(ent).insert(Selected); } else { cmd.remove_resource::(); } @@ -152,9 +141,23 @@ fn update_selection( ) }) { cmd.remove_resource::(); + } +} - for ent in q_selected.iter() { - cmd.entity(ent).remove::(); +fn nearest_selectable_parent( + mut target: Entity, + q_parents: &Query<&Parent>, + q_selectable: &Query<&Selectable>, +) -> Option { + loop { + if q_selectable.get(target).is_ok() { + break Some(target); + } + + if let Ok(parent) = q_parents.get(target) { + target = parent.get(); + } else { + break None; } } } @@ -192,7 +195,8 @@ fn finalize_delete(mut cmd: Commands, q: Query>) fn draw_selection_outline( mut cmd: Commands, - q_selected: Query<&Children, With>, + r_selection: Option>, + q_children: Query<&Children>, q_meshes: Query< Entity, ( @@ -209,8 +213,11 @@ fn draw_selection_outline( cmd.entity(ent).remove::(); } - for children in q_selected.iter() { - for &child in children.iter() { + if let Some(selected_children) = r_selection + .as_ref() + .and_then(|selection| q_children.get(***selection).ok()) + { + for &child in selected_children.iter() { if q_meshes.contains(child) { cmd.entity(child).insert(OutlineBundle { outline: OutlineVolume { @@ -233,7 +240,7 @@ mod wasm { }; use wasm_bindgen::prelude::*; - use super::{Selected, Selection}; + use super::Selection; use crate::wasm::{self, FromStr, StaticBuffer, WasmMessageBuffer}; #[wasm_bindgen(typescript_custom_section)] @@ -273,7 +280,6 @@ declare global { pub(super) fn update_selection_from_frontend(mut cmd: Commands) { if let Some(entity) = SELECTION_REQUEST.take_message() { cmd.insert_resource(Selection(entity)); - cmd.entity(entity).insert(Selected); } } } diff --git a/studio/src/ui/inspector.rs b/studio/src/ui/inspector.rs index 851a4bf..e9fa975 100644 --- a/studio/src/ui/inspector.rs +++ b/studio/src/ui/inspector.rs @@ -19,7 +19,7 @@ use space_editor::{ use wasm_bindgen::prelude::*; use crate::{ - interaction::Selected, + interaction::Selection, wasm::{self, StaticBuffer, WasmMessageBuffer}, }; @@ -69,7 +69,8 @@ impl Plugin for InspectorPlugin { .add_systems( Update, ( - update_inspect_by_select_change::>, + update_inspect_by_select_change::> + .run_if(resource_changed_or_removed::()), update_inspect_by_remove::>, update_inspect_by_change::>, notify_asset_change_system::, @@ -125,7 +126,8 @@ impl AppInspectable for App { .add_systems( Update, ( - update_inspect_by_select_change::, + update_inspect_by_select_change:: + .run_if(resource_changed_or_removed::()), update_inspect_by_remove::, update_inspect_by_change::, ) @@ -158,7 +160,8 @@ impl AppInspectable for App { .add_systems( Update, ( - update_inspect_by_select_change::>, + update_inspect_by_select_change::> + .run_if(resource_changed_or_removed::()), update_inspect_by_remove::>, update_inspect_by_change::>, notify_asset_change_system::, @@ -227,12 +230,10 @@ fn update_field_by_ui(world: &mut World) { return; }; - let mut selected_query = unsafe_world - .world_mut() - .query_filtered::>(); - let Ok(entity) = selected_query.get_single(unsafe_world.world_mut()) else { + let Some(selection) = unsafe_world.world_mut().get_resource::() else { return; }; + let entity = **selection; let getters = unsafe_world.get_resource_mut::().unwrap(); @@ -320,16 +321,8 @@ pub struct EntityDescription { fn update_inspect_by_select_change( mut ev_inspect: EventWriter>, - q_added: Query<(), Added>, - q_removed: RemovedComponents, ) { - if !q_added.is_empty() { - ev_inspect.send(UpdateInspect::::default()); - } - - if !q_removed.is_empty() { - ev_inspect.send(UpdateInspect::::default()); - } + ev_inspect.send(UpdateInspect::::default()); } fn update_inspect_by_remove( @@ -342,16 +335,20 @@ fn update_inspect_by_remove( } fn update_inspect_by_change( + r_selection: Option>, mut ev_inspect: EventWriter>, - q_t_changed: Query<&T, (With, Changed)>, + q_t_changed: Query<&T, Changed>, ) { - if q_t_changed.iter().next().is_some() { - ev_inspect.send(UpdateInspect::::default()); + if let Some(selection) = r_selection { + if q_t_changed.contains(**selection) { + ev_inspect.send(UpdateInspect::::default()); + } } } fn auto_serialize_system( - q_t: Query<&T, With>, + r_selection: Option>, + q_t: Query<&T>, mut description: ResMut, registry: Res, mut ev_inspect: EventReader>, @@ -363,7 +360,7 @@ fn auto_serialize_system( ev_inspect.clear(); - if let Some(t) = q_t.iter().next() { + if let Some(t) = r_selection.and_then(|selection| q_t.get(**selection).ok()) { let registry = registry.read(); let json = serde_json::to_value(ReflectSerializer::new(t, ®istry)).unwrap(); info!("json: {:?}", json.to_string()); @@ -384,12 +381,16 @@ fn auto_serialize_system( } fn notify_asset_change_system( + r_selection: Option>, mut ev_inspect: EventWriter>>, mut asset_changing: EventReader>, - mut selected: Query<&Handle, With>, + q_handles: Query<&Handle>, ) { for ev in asset_changing.read() { - for handle in selected.iter_mut() { + if let Some(handle) = r_selection + .as_ref() + .and_then(|selection| q_handles.get(***selection).ok()) + { if ev.is_modified(handle.id()) { ev_inspect.send(UpdateInspect::>::default()); } @@ -398,7 +399,8 @@ fn notify_asset_change_system( } fn auto_serialize_asset( - q_t: Query<&Handle, With>, + r_selection: Option>, + q_t: Query<&Handle>, mut description: ResMut, registry: Res, mut ev_inspect: EventReader>>, @@ -411,7 +413,7 @@ fn auto_serialize_asset( ev_inspect.clear(); - if let Some(handle) = q_t.iter().next() { + if let Some(handle) = r_selection.and_then(|selection| q_t.get(**selection).ok()) { if let Some(asset) = assets.get(handle) { let registry = registry.read(); info!(