Merge pull request #833 from vbasky/fix/785-explorer-timeline-drop

OS drops on an empty Timeline create a sequence
This commit is contained in:
Brandon Thomas
2026-10-11 19:18:33 -04:00
2 changed files with 57 additions and 0 deletions
+20
View File
@@ -666,6 +666,26 @@ fn empty_state(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
{
let _ = app.session.execute("file.newSequence", json!({"fromItem": item.0}));
}
// OS files dropped on an empty Timeline: import already happened; create a sequence from the first item
if let Some((items, p)) = app.pending_timeline_drop.clone()
&& rect.contains(p)
&& let Some((first, rest)) = items.split_first()
{
app.pending_timeline_drop = None;
if let Err(e) = app.session.execute("file.newSequence", json!({"fromItem": first.0})) {
app.ui.status = e.to_string();
} else {
let mut t = app.session.project.item(*first).map(|i| i.duration()).unwrap_or(Tick::ZERO);
for item in rest {
let params = json!({"item": item.0, "time": t.0, "insert": false});
if let Err(e) = app.session.execute("timeline.place", params) {
app.ui.status = e.to_string();
} else {
t = Tick(t.0.saturating_add(app.session.project.item(*item).map(|i| i.duration().0.max(0)).unwrap_or(0)));
}
}
}
}
}
/// The through-edit mark: two small triangles pointing at the cut, centred on the track, with a
+37
View File
@@ -143,6 +143,43 @@ fn tone_dropped_on_a_video_row_goes_to_the_audio_track_without_error() {
assert_eq!(clip_counts(&h), (0, 1), "status: {}", h.state().ui.status);
}
/// Empty project: no sequence yet. Dropping on the empty Timeline must import *and* make a sequence.
fn empty_harness() -> Harness<'static, FilmcraftApp> {
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 980.0)).build_eframe(move |_cc| FilmcraftApp::new(Session::default()));
for _ in 0..4 {
step(&mut h);
}
h
}
fn drop_on_empty_timeline(h: &mut Harness<'static, FilmcraftApp>, path: &Path) {
let panel = control_rect(h, "panel.Timeline");
pointer_to(h, panel.center());
h.input_mut().dropped_files.push(std::sync::Arc::new(OsFile(path.to_path_buf())));
step(h);
h.input_mut().dropped_files.clear();
step(h);
}
#[test]
fn still_dropped_on_empty_timeline_creates_a_sequence() {
let mut h = empty_harness();
assert!(h.state().session.active_sequence().is_none());
drop_on_empty_timeline(&mut h, &still("drop_still_empty_timeline.png"));
assert_eq!(h.state().session.project.items.len(), 2, "still plus the new sequence");
assert!(h.state().session.active_sequence().is_some(), "status: {}", h.state().ui.status);
assert_eq!(clip_counts(&h), (1, 0), "status: {}", h.state().ui.status);
}
#[test]
fn with_drop_import_off_empty_timeline_does_not_create_a_sequence() {
let mut h = empty_harness();
h.state_mut().session.prefs.timeline.drop_import_to_timeline = false;
drop_on_empty_timeline(&mut h, &still("drop_still_empty_pref_off.png"));
assert_eq!(h.state().session.project.items.len(), 1, "imported to the bin only");
assert!(h.state().session.active_sequence().is_none());
}
#[test]
fn with_drop_import_off_the_file_is_only_imported() {
let mut h = harness();