mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Merge pull request #709 from Ni-zav/fix/684-empty-viewer-project-drop-s20261011T035800Z-cf39
fix(viewer): create a composition when dropping footage onto an empty viewer
This commit is contained in:
commit
196dd80104
2 files changed
+70
No files matched your search
@@ -2296,6 +2296,36 @@ fn empty_state(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
let _ = crate::menus::invoke(app, &ui.ctx().clone(), "file.import", json!({}));
|
||||
}
|
||||
app.auto.add("viewer.empty.import", b2, "New Composition From Footage");
|
||||
// A Project item dropped on the empty viewer creates the first composition,
|
||||
// just like dropping it onto an empty Timeline (#684). Multiple selected
|
||||
// items use the existing New Comp from Selection dialog.
|
||||
let ctx = ui.ctx().clone();
|
||||
if let Some(crate::panels::DragPayload::Item(dragged)) = egui::DragAndDrop::payload::<crate::panels::DragPayload>(&ctx).as_deref()
|
||||
&& ui.rect_contains_pointer(rect)
|
||||
{
|
||||
let items: Vec<effectcraft_engine::project::ItemId> = crate::panels::project_drop_items(app, *dragged)
|
||||
.into_iter()
|
||||
.map(effectcraft_engine::project::ItemId)
|
||||
.filter(|id| {
|
||||
app.session.project.item(*id).is_some_and(|item| match &item.kind {
|
||||
effectcraft_engine::project::ItemKind::Footage(f) => f.kind != effectcraft_engine::project::FootageKind::Data,
|
||||
effectcraft_engine::project::ItemKind::Solid(_) | effectcraft_engine::project::ItemKind::Comp(_) => true,
|
||||
_ => false,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if !items.is_empty() {
|
||||
p.rect_stroke(rect.shrink(2.0), 0.0, Stroke::new(2.0, t.accent), StrokeKind::Inside);
|
||||
app.auto.add("viewer.newCompDrop", rect, "Drop footage to create a composition");
|
||||
if ctx.input(|i| i.pointer.any_released()) {
|
||||
app.session.state.project_selection = items;
|
||||
if let Err(e) = crate::menus::invoke(app, &ctx, "file.newCompFromSelection", json!({})) {
|
||||
app.ui.status = e;
|
||||
}
|
||||
egui::DragAndDrop::clear_payload(&ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `#rrggbb` → sRGB bytes.
|
||||
|
||||
@@ -252,6 +252,46 @@ fn multiple_selected_footage_dropped_on_empty_timeline_asks_how_to_create_comps(
|
||||
assert!(h.state().session.active_comp().is_none(), "no composition until the user chooses");
|
||||
}
|
||||
|
||||
/// #684: dropping imported footage onto the empty Composition viewer creates
|
||||
/// a composition that matches the source, with that source as its first layer.
|
||||
#[test]
|
||||
fn project_footage_dropped_on_empty_viewer_creates_first_comp() {
|
||||
let mut s = Session::default();
|
||||
let footage = s.execute("file.importPlaceholder", json!({"name": "Landscape.png", "width": 640, "height": 360, "frameRate": 24, "duration": 5}))
|
||||
.unwrap()["item"].as_u64().unwrap();
|
||||
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
|
||||
h.run_steps(3);
|
||||
assert!(h.state().session.active_comp().is_none());
|
||||
let from = rect(&h, &format!("project.item.{footage}.name")).center();
|
||||
let viewer = rect(&h, "panel.Composition");
|
||||
drag(&mut h, from, pos2(viewer.min.x + 40.0, viewer.min.y + 70.0), Modifiers::NONE);
|
||||
let comp = h.state().session.active_comp().expect("created a composition");
|
||||
assert_eq!((comp.width, comp.height), (640, 360));
|
||||
assert_eq!(comp.frame_rate, effectcraft_engine::time::FrameRate::from_f64(24.0));
|
||||
assert_eq!(comp.duration.seconds(), 5.0);
|
||||
assert_eq!(comp.layers.len(), 1);
|
||||
assert_eq!(comp.layers[0].name, "Landscape.png");
|
||||
assert!(h.state().dialog.is_none());
|
||||
}
|
||||
|
||||
/// Several dragged sources must ask whether to create one or multiple comps,
|
||||
/// rather than silently discarding the other selected items.
|
||||
#[test]
|
||||
fn multiple_selected_footage_dropped_on_empty_viewer_asks_how_to_create_comps() {
|
||||
let mut s = Session::default();
|
||||
let first = s.execute("file.importPlaceholder", json!({"name": "First.png", "width": 160, "height": 90})).unwrap()["item"].as_u64().unwrap();
|
||||
let second = s.execute("file.importPlaceholder", json!({"name": "Second.png", "width": 300, "height": 200})).unwrap()["item"].as_u64().unwrap();
|
||||
s.state.project_selection = vec![effectcraft_engine::project::ItemId(first), effectcraft_engine::project::ItemId(second)];
|
||||
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
|
||||
h.run_steps(3);
|
||||
let from = rect(&h, &format!("project.item.{first}.name")).center();
|
||||
let viewer = rect(&h, "panel.Composition");
|
||||
drag(&mut h, from, pos2(viewer.min.x + 40.0, viewer.min.y + 70.0), Modifiers::NONE);
|
||||
assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::Form));
|
||||
assert!(h.state().auto.find("form.field.single").is_some());
|
||||
assert!(h.state().session.active_comp().is_none(), "no composition until the user chooses");
|
||||
}
|
||||
|
||||
/// A file dropped on the window (egui hands it over as a dropped file).
|
||||
#[derive(Debug)]
|
||||
struct Dropped(std::path::PathBuf);
|
||||
|
||||
Reference in new issue
Block a user