diff --git a/crates/op-editor-core/src/tests_mutators.rs b/crates/op-editor-core/src/tests_mutators.rs index 0e0f051ec..51d56e654 100644 --- a/crates/op-editor-core/src/tests_mutators.rs +++ b/crates/op-editor-core/src/tests_mutators.rs @@ -172,6 +172,25 @@ fn delete_selected_returns_false_when_unselected() { assert!(!s.delete_selected()); } +#[test] +fn delete_selected_removes_every_node_in_the_set() { + let mut s = state_with(vec![ + rect("n1", "A", 0.0, 0.0, 10.0, 10.0), + rect("n2", "B", 20.0, 0.0, 10.0, 10.0), + rect("n3", "C", 40.0, 0.0, 10.0, 10.0), + ]); + s.clear_selection(); + s.toggle_selection(NodeId::new("n1")); + s.toggle_selection(NodeId::new("n2")); + assert_eq!(s.selection_count(), 2); + assert!(s.delete_selected()); + assert!(find_node(s.active_children(), &NodeId::new("n1")).is_none()); + assert!(find_node(s.active_children(), &NodeId::new("n2")).is_none()); + assert!(find_node(s.active_children(), &NodeId::new("n3")).is_some()); + assert_eq!(s.active_children().len(), 1); + assert!(s.selection.is_empty()); +} + #[test] fn delete_selected_protects_ancestor_of_locked_descendant() { let mut child = rect("n61", "child", 0.0, 0.0, 10.0, 10.0); diff --git a/crates/op-host-native/src/widget_host/press.rs b/crates/op-host-native/src/widget_host/press.rs index 35a1f8e67..a490d5c97 100644 --- a/crates/op-host-native/src/widget_host/press.rs +++ b/crates/op-host-native/src/widget_host/press.rs @@ -28,14 +28,22 @@ impl WidgetHostNative { use op_editor_ui::widgets::layer_context_menu::LayerContextAction as A; match (action, target) { (A::Duplicate, T::Layer(id)) => { - self.editor_state.set_single_selection(id); + // Act on the whole multi-selection when the right-clicked + // row is part of it; otherwise retarget to just this row. + if !self.editor_state.is_selected(&id) { + self.editor_state.set_single_selection(id); + } self.editor_state.commit_history(); let _ = self .editor_state .duplicate_selected(&mut self.next_node_id, 10.0); } (A::Delete, T::Layer(id)) => { - self.editor_state.set_single_selection(id); + // Keep the multi-selection so Delete removes every selected + // layer, not just the right-clicked one. + if !self.editor_state.is_selected(&id) { + self.editor_state.set_single_selection(id); + } self.editor_state.commit_history(); let _ = self.editor_state.delete_selected(); } diff --git a/crates/op-host-web/src/widget_host/press.rs b/crates/op-host-web/src/widget_host/press.rs index d843836b2..e7b9e521d 100644 --- a/crates/op-host-web/src/widget_host/press.rs +++ b/crates/op-host-web/src/widget_host/press.rs @@ -63,7 +63,15 @@ impl WidgetHost { match panel.hit_test(layer_rect, Point2D::new(x, y)) { Some(LayerPanelHit::Layer(id)) => { let ec_id = id.clone(); - self.editor_state.set_single_selection(ec_id.clone()); + // Right-clicking a row that's part of a multi-selection + // keeps the whole selection (so context-menu Delete / + // Duplicate act on every selected layer); right-clicking + // outside the selection retargets to just that row. + if !(self.editor_state.is_selected(&ec_id) + && self.editor_state.selection_count() > 1) + { + self.editor_state.set_single_selection(ec_id.clone()); + } self.editor_state.editor_ui.layer_context_menu = Some(LayerContextMenuState { target: LayerContextTarget::Layer(ec_id), anchor_x: x, @@ -107,14 +115,22 @@ impl WidgetHost { use op_editor_ui::widgets::layer_context_menu::LayerContextAction as A; match (action, target) { (A::Duplicate, T::Layer(id)) => { - self.editor_state.set_single_selection(id); + // Act on the whole multi-selection when the right-clicked + // row is part of it; otherwise retarget to just this row. + if !self.editor_state.is_selected(&id) { + self.editor_state.set_single_selection(id); + } self.editor_state.commit_history(); let _ = self .editor_state .duplicate_selected(&mut self.next_node_id, 10.0); } (A::Delete, T::Layer(id)) => { - self.editor_state.set_single_selection(id); + // Keep the multi-selection so Delete removes every selected + // layer, not just the right-clicked one. + if !self.editor_state.is_selected(&id) { + self.editor_state.set_single_selection(id); + } self.editor_state.commit_history(); let _ = self.editor_state.delete_selected(); }