Project: a single unselected folder or data-file drag still reports why; index math saturates (#634 follow-up)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
Claude
2026-10-10 18:51:45 +00:00
parent 72b2e9774e
commit 7a95a0d138
4 changed files with 22 additions and 19 deletions
+3 -1
View File
@@ -88,7 +88,9 @@ pub(crate) fn project_drop_items(app: &EffectcraftApp, dragged: u64) -> Vec<u64>
let candidates: Vec<u64> = if selected.iter().any(|id| id.0 == dragged) {
selected.iter().map(|id| id.0).collect()
} else {
vec![dragged]
// One unselected item goes through as is, so a folder or data file still gets the
// engine's error instead of a silent no-op.
return vec![dragged];
};
candidates
.into_iter()
+1 -1
View File
@@ -2964,7 +2964,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
crate::panels::DragPayload::Item(id) => {
// Insert each selected item at the drop point, in selection order.
for (offset, item) in crate::panels::project_drop_items(app, *id).into_iter().enumerate() {
actions.push(("layer.addItem".into(), json!({"item": item, "index": index + offset, "time": time.clone()})));
actions.push(("layer.addItem".into(), json!({"item": item, "index": index.saturating_add(offset), "time": time.clone()})));
}
}
crate::panels::DragPayload::Files(paths) => {
+10 -11
View File
@@ -24,12 +24,15 @@ pub(crate) fn show(app: &mut EffectcraftApp, ui: &egui::Ui, painter: &egui::Pain
let stroke = Stroke::new(2.0, app.tokens.accent);
let at = map.to_comp(ptr);
let actions: Vec<(&str, Value)> = match payload.as_ref() {
DragPayload::Effect(effect) => layer_at(ectx, at).map(|l| {
if let Some((_, q, _)) = layer_quad(ectx, l) {
painter.add(egui::Shape::closed_line(q.iter().map(|c| map.to_screen(*c)).collect(), stroke));
}
("effect.apply", json!({"effect": effect, "layers": [l.id.0]}))
}).into_iter().collect(),
DragPayload::Effect(effect) => layer_at(ectx, at)
.map(|l| {
if let Some((_, q, _)) = layer_quad(ectx, l) {
painter.add(egui::Shape::closed_line(q.iter().map(|c| map.to_screen(*c)).collect(), stroke));
}
("effect.apply", json!({"effect": effect, "layers": [l.id.0]}))
})
.into_iter()
.collect(),
DragPayload::Item(item) => {
// The layer's frame, centred on the pointer.
if let Some([w, h]) = app.session.project.item(ItemId(*item)).and_then(|it| frame_size(&it.kind, ectx.comp.pixel_aspect)) {
@@ -37,11 +40,7 @@ pub(crate) fn show(app: &mut EffectcraftApp, ui: &egui::Ui, painter: &egui::Pain
}
// New layers go above the current selection, so insert in reverse
// to preserve the order of the dragged Project items.
crate::panels::project_drop_items(app, *item)
.into_iter()
.rev()
.map(|item| ("layer.addItem", json!({"item": item, "position": at})))
.collect()
crate::panels::project_drop_items(app, *item).into_iter().rev().map(|item| ("layer.addItem", json!({"item": item, "position": at}))).collect()
}
DragPayload::Files(paths) => vec![("mediaBrowser.import", json!({"paths": paths, "addToComp": true, "position": at}))],
DragPayload::Property { .. } => vec![],
+8 -6
View File
@@ -129,10 +129,11 @@ fn project_items_dropped_on_the_viewer_land_under_the_pointer() {
fn dragging_selected_project_items_adds_every_item_to_the_timeline() {
let (mut h, clip) = harness();
let main = h.state().session.active_comp_id().unwrap().0;
let other = h.state_mut().session.execute("comp.new", json!({"name": "Other", "width": 80, "height": 60, "frameRate": 30, "duration": 1})).unwrap()["comp"].as_u64().unwrap();
let other = h.state_mut().session.execute("comp.new", json!({"name": "Other", "width": 80, "height": 60, "frameRate": 30, "duration": 1})).unwrap()["comp"]
.as_u64()
.unwrap();
h.state_mut().session.execute("comp.open", json!({"comp": main})).unwrap();
h.state_mut().session.state.project_selection =
vec![effectcraft_engine::project::ItemId(clip), effectcraft_engine::project::ItemId(other)];
h.state_mut().session.state.project_selection = vec![effectcraft_engine::project::ItemId(clip), effectcraft_engine::project::ItemId(other)];
h.run_steps(3);
let from = rect(&h, &format!("project.item.{clip}.name")).center();
let top = rect(&h, &format!("timeline.layer.{}.row", layer_id(&h, "Top")));
@@ -145,10 +146,11 @@ fn dragging_selected_project_items_adds_every_item_to_the_timeline() {
fn dragging_selected_project_items_adds_every_item_to_the_viewer() {
let (mut h, clip) = harness();
let main = h.state().session.active_comp_id().unwrap().0;
let other = h.state_mut().session.execute("comp.new", json!({"name": "Other", "width": 80, "height": 60, "frameRate": 30, "duration": 1})).unwrap()["comp"].as_u64().unwrap();
let other = h.state_mut().session.execute("comp.new", json!({"name": "Other", "width": 80, "height": 60, "frameRate": 30, "duration": 1})).unwrap()["comp"]
.as_u64()
.unwrap();
h.state_mut().session.execute("comp.open", json!({"comp": main})).unwrap();
h.state_mut().session.state.project_selection =
vec![effectcraft_engine::project::ItemId(clip), effectcraft_engine::project::ItemId(other)];
h.state_mut().session.state.project_selection = vec![effectcraft_engine::project::ItemId(clip), effectcraft_engine::project::ItemId(other)];
h.run_steps(3);
let from = rect(&h, &format!("project.item.{clip}.name")).center();
let to = comp_to_screen(&h.ctx, [80.0, 45.0]).unwrap();