Project panel: items dropped on New Composition make a comp (#227)

Dragging Project items onto the Create a new Composition button at the
bottom of the Project panel did nothing. As in After Effects, the
button now takes the drop and runs File > New Comp from Selection with
the dragged items (the selection when the dragged item is in it): one
item becomes a comp of its size, frame rate and duration holding it,
and several open the New Composition from Selection dialog. The button
is outlined while items are dragged over it.

Test: project_items_dropped_on_new_comp_make_a_composition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-08 12:35:35 +07:00
parent fab6dedd7c
commit 6b95b7a182
2 changed files with 47 additions and 2 deletions

View File

@ -2,8 +2,9 @@
//! search, item list with columns (Name, Label, then the visible optional columns: Type, Size,
//! Media Duration, Frame Rate, File Path, Comment — shown or hidden from the header's context
//! menu), folders (drag items into and out of them), renaming (Enter, or double-click the name),
//! the label colour picker, and the bottom bar (interpret, new folder, new comp, bit depth,
//! delete). Edits are `project.*` engine commands, so they are undoable.
//! the label colour picker, and the bottom bar (interpret, new folder, new comp: click, or drop
//! items on it for a comp from them, bit depth, delete). Edits are engine commands, so they are
//! undoable.
use std::sync::{Arc, Mutex};
@ -699,6 +700,21 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
_ => app.ui.status = "Interpret Footage: select a footage item".into(),
}
}
// Items dropped on Create a new Composition: New Comp from Selection with them (After
// Effects; several items ask how in its dialog).
if id == "newComp"
&& let Some(DragPayload::Item(iid)) = egui::DragAndDrop::payload::<DragPayload>(&ctx).as_deref()
&& ui.rect_contains_pointer(r)
{
p.rect_stroke(r, 4.0, Stroke::new(2.0, t.accent), egui::StrokeKind::Inside);
if ctx.input(|i| i.pointer.any_released()) {
if !app.session.state.project_selection.contains(&ItemId(*iid)) {
app.session.state.project_selection = vec![ItemId(*iid)];
}
actions.push(("file.newCompFromSelection".into(), json!({})));
egui::DragAndDrop::clear_payload(&ctx);
}
}
x += 26.0;
}
let depth = app.session.project.settings.bit_depth.label();

View File

@ -136,3 +136,32 @@ fn effects_dropped_on_the_viewer_go_on_the_layer_under_the_pointer() {
let effects = |id: u64| comp.layer(effectcraft_engine::project::LayerId(id)).unwrap().effects().map_or(0, |fx| fx.groups().count());
assert_eq!((effects(top), effects(middle)), (1, 0), "on Top, which is under the pointer");
}
/// #227: Project items dropped on Create a new Composition (the Project panel's footer) make a
/// composition from them, as File ▸ New Comp from Selection does; several items ask how in its
/// dialog.
#[test]
fn project_items_dropped_on_new_comp_make_a_composition() {
let (mut h, clip) = harness();
let comps = |h: &Harness<'_, EffectcraftApp>| h.state().session.project.comps().count();
let before = comps(&h);
let item = rect(&h, &format!("project.item.{clip}.name")).center();
let button = rect(&h, "project.newComp").center();
drag(&mut h, item, button, Modifiers::NONE);
assert_eq!(comps(&h), before + 1);
let comp = h.state().session.active_comp().unwrap();
assert_eq!((comp.width, comp.height, comp.duration.seconds()), (160, 90, 1.0), "the item's settings");
assert_eq!(stack(&h), ["Clip"], "holding the item");
assert_ne!(h.state().session.active_comp_id().map(|c| c.0), Some(clip), "a new comp, open");
assert!(h.state().dialog.is_none());
// Two selected items, one dragged: New Composition from Selection asks how.
let main = h.state().session.project.items.values().find(|i| i.name == "Main").unwrap().id;
h.state_mut().session.state.project_selection = vec![effectcraft_engine::project::ItemId(clip), main];
h.run_steps(2);
let item = rect(&h, &format!("project.item.{clip}.name")).center();
drag(&mut h, item, button, Modifiers::NONE);
assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::Form));
assert!(h.state().auto.find("form.field.single").is_some(), "the New Composition from Selection dialog");
assert_eq!(comps(&h), before + 1, "nothing made yet");
}