diff --git a/crates/ui-egui/src/edit_window/input.rs b/crates/ui-egui/src/edit_window/input.rs index ad7a2225..8529db9a 100644 --- a/crates/ui-egui/src/edit_window/input.rs +++ b/crates/ui-egui/src/edit_window/input.rs @@ -533,15 +533,18 @@ fn clip_context_menu(app: &mut SoundApp, ui: &mut Ui, clip: Option) { app.dialogs.open_rename_clip(c, &name); } if ui.button("Delete").clicked() { - let selected: Vec = app.engine.session().edit.selected_clips.iter().map(|id| id.0).collect(); + let s = app.engine.session(); + let selected: Vec = s.edit.selected_clips.iter().map(|id| id.0).collect(); + let track: Vec = s.find_clip(c).map(|(t, _)| t.0).into_iter().collect(); if !in_selection { let _ = app.run("edit.select", json!({"clips": [c.0]})); let _ = app.run("edit.clear", json!({})); } else if selected.is_empty() { let _ = app.run("edit.clear", json!({})); } else { - // Selected clips go by id (an empty range), so clips between them that the span covers stay. - let _ = app.run("edit.clear", json!({"clips": selected, "start": 0})); + // Selected clips go by id (an empty range), so clips between them that the span covers stay. The clicked + // clip's track satisfies Clear's track check even when the track selection was emptied meanwhile. + let _ = app.run("edit.clear", json!({"clips": selected, "tracks": track, "start": 0})); } } } diff --git a/crates/ui-egui/tests/it/clip_menu.rs b/crates/ui-egui/tests/it/clip_menu.rs index 7330e319..bfac15b6 100644 --- a/crates/ui-egui/tests/it/clip_menu.rs +++ b/crates/ui-egui/tests/it/clip_menu.rs @@ -135,6 +135,26 @@ fn delete_on_selected_clips_keeps_the_unselected_clips_between_them() { assert_eq!(ids(&h, "Snare"), left, "only the selected Snare clip should be removed"); } +/// Selected clips are deleted even when the track selection was emptied after selecting them (Edit > Selection > +/// Remove Edit from Top keeps the clip selection). +#[test] +fn delete_on_selected_clips_works_without_a_track_selection() { + let mut h = demo(); + let kick = clip_count(&h, "Kick"); + let clips: Vec = { + let s = h.state().engine.session(); + s.tracks.iter().filter(|t| t.name == "Kick").flat_map(|t| t.clips()).map(|c| c.id.0).collect() + }; + let e = &mut h.state_mut().engine; + e.execute("edit.select", &serde_json::json!({ "clips": clips })).unwrap(); + e.execute("edit.remove_selection_top", &serde_json::json!({})).unwrap(); + assert!(e.session().edit.selected_tracks.is_empty() && !e.session().edit.selected_clips.is_empty()); + + kick_menu(&mut h, "Delete"); + + assert_eq!(clip_count(&h, "Kick"), kick - clips.len(), "the selected Kick clips should be removed"); +} + /// The menu's other entries act on the whole selection too. #[test] fn mute_on_a_selected_clip_mutes_every_selected_clip() {