From 7a95a0d138c8eafd1156c1ba7aa0e627c5f68f4a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 18:51:45 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f --- crates/ui-egui/src/panels/mod.rs | 4 +++- crates/ui-egui/src/panels/timeline.rs | 2 +- crates/ui-egui/src/panels/viewer_drop.rs | 21 ++++++++++----------- crates/ui-egui/tests/ui_drag_drop.rs | 14 ++++++++------ 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/crates/ui-egui/src/panels/mod.rs b/crates/ui-egui/src/panels/mod.rs index 76691dce1..79ef9c2bc 100644 --- a/crates/ui-egui/src/panels/mod.rs +++ b/crates/ui-egui/src/panels/mod.rs @@ -88,7 +88,9 @@ pub(crate) fn project_drop_items(app: &EffectcraftApp, dragged: u64) -> Vec let candidates: Vec = 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() diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 32c52b47a..f91987c08 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -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) => { diff --git a/crates/ui-egui/src/panels/viewer_drop.rs b/crates/ui-egui/src/panels/viewer_drop.rs index dec8d3802..2d42eb2c9 100644 --- a/crates/ui-egui/src/panels/viewer_drop.rs +++ b/crates/ui-egui/src/panels/viewer_drop.rs @@ -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![], diff --git a/crates/ui-egui/tests/ui_drag_drop.rs b/crates/ui-egui/tests/ui_drag_drop.rs index 56f9ffd72..3dee2727e 100644 --- a/crates/ui-egui/tests/ui_drag_drop.rs +++ b/crates/ui-egui/tests/ui_drag_drop.rs @@ -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();