From f9db3afd347898378aeb0c33ddde6a367569b0c8 Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sun, 11 Oct 2026 11:28:18 +0200 Subject: [PATCH] #678: dropped Photoshop files show the Import Photoshop File dialog Problem: dropping a PSD/PSB file on the window imported it straight away. The drop handler always passed `importAs` (Settings > Import > Default Drag Import As) to `file.import`, and the Import Photoshop File form only opens when `importAs` is missing, so the dialog that File > Import > File... shows never appeared for drops. Fix: a drop that includes a Photoshop file leaves `importAs` out, so the form asks (other drops keep passing the preference). For dropped files the form's Import Kind starts at Default Drag Import As; File > Import keeps starting at Composition. The engine already falls back to the preference for `drag` imports without `importAs`. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/forms.rs | 19 +++++++++--------- crates/ui-egui/src/prefs_live.rs | 7 ++++++- crates/ui-egui/tests/ui_drag_drop.rs | 30 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/crates/ui-egui/src/panels/forms.rs b/crates/ui-egui/src/panels/forms.rs index 039ebb69..b04e017b 100644 --- a/crates/ui-egui/src/panels/forms.rs +++ b/crates/ui-egui/src/panels/forms.rs @@ -215,16 +215,15 @@ pub fn open_form(app: &mut EffectcraftApp, id: &str, p: &Value) -> bool { let lt = layer.map(|l| l.layer_time(t)).unwrap_or(t); let base = p.clone(); let (title, fields): (String, Vec) = match id { - // Photoshop files: Import Kind (Footage / Composition / – Retain Layer Sizes). - "file.import" if !has(p, &["importAs"]) && is_layered(p) => ( - "Import Photoshop File".into(), - vec![Field::choice( - "importAs", - "Import Kind", - &[("Footage", json!("footage")), ("Composition", json!("composition")), ("Composition - Retain Layer Sizes", json!("compositionLayerSizes"))], - 1, - )], - ), + // Photoshop files: Import Kind (Footage / Composition / – Retain Layer Sizes). Dropped + // files start at Settings ▸ Import ▸ Default Drag Import As. + "file.import" if !has(p, &["importAs"]) && is_layered(p) => { + let kinds = + [("Footage", json!("footage")), ("Composition", json!("composition")), ("Composition - Retain Layer Sizes", json!("compositionLayerSizes"))]; + let drag = p.get("drag").and_then(Value::as_bool).unwrap_or(false); + let sel = if drag { kinds.iter().position(|(_, v)| v.as_str() == Some(s.prefs.drag_import_as())).unwrap_or(0) } else { 1 }; + ("Import Photoshop File".into(), vec![Field::choice("importAs", "Import Kind", &kinds, sel)]) + } // PDF / Illustrator files: Import Kind and the page. "file.import" if !has(p, &["importAs", "page"]) && is_pdf(p) => ( "Import PDF / Illustrator File".into(), diff --git a/crates/ui-egui/src/prefs_live.rs b/crates/ui-egui/src/prefs_live.rs index b18d3e87..321e432e 100644 --- a/crates/ui-egui/src/prefs_live.rs +++ b/crates/ui-egui/src/prefs_live.rs @@ -91,14 +91,19 @@ pub fn dropped_files(app: &mut EffectcraftApp, ui: &egui::Ui, bounds: Rect) { // Dropped files import as stills and folders' numbered stills as sequences; Alt-drag // imports a folder's files one by one (After Effects). let alt = ctx.input(|i| i.modifiers.alt); + // A dropped Photoshop file asks how to import it (the Import Photoshop File dialog, its + // Import Kind starting at Default Drag Import As), as File ▸ Import ▸ File… does (#678). + let layered = rest.iter().any(|p| p.rsplit('.').next().is_some_and(|e| matches!(e.to_ascii_lowercase().as_str(), "psd" | "psb"))); let mut params = json!({ "paths": rest, "drag": true, - "importAs": app.session.prefs.drag_import_as(), "addToComp": at.is_some(), "position": at, "background": true }); + if !layered { + params["importAs"] = json!(app.session.prefs.drag_import_as()); + } if alt { params["sequence"] = json!(false); } diff --git a/crates/ui-egui/tests/ui_drag_drop.rs b/crates/ui-egui/tests/ui_drag_drop.rs index ce09d7fd..05c40d5e 100644 --- a/crates/ui-egui/tests/ui_drag_drop.rs +++ b/crates/ui-egui/tests/ui_drag_drop.rs @@ -15,6 +15,7 @@ use effectcraft_ui_egui::dock::PanelKind; use effectcraft_ui_egui::panels::viewer::{comp_to_screen, last_fit}; use egui::{Event, Modifiers, Pos2, Rect, pos2, vec2}; use egui_kittest::Harness; +use egui_kittest::kittest::Queryable; use serde_json::json; /// Comp "Main" (4 s at 30 fps, current time 1 s) with solids Top, Middle and Bottom, and comp @@ -379,6 +380,35 @@ fn dropped_file_on_viewer_imports_and_adds_layer_at_pointer() { assert!((p[0] - 80.0).abs() <= tol && (p[1] - 45.0).abs() <= tol, "{p:?}"); } +/// #678: a dropped Photoshop file asks how to import it, as File ▸ Import ▸ File… does; the +/// Import Kind starts at Settings ▸ Import ▸ Default Drag Import As, and OK imports the drop. +#[test] +fn dropped_photoshop_file_shows_the_import_photoshop_file_dialog() { + let (mut h, _) = harness(); + h.state_mut().session.importer = Some(Arc::new(Gated { started: AtomicUsize::new(0), allow: Arc::new(AtomicUsize::new(usize::MAX)) })); + h.state_mut().session.prefs.import.drag_import_as = "compLayerSizes".into(); + h.event(Event::PointerMoved(rect(&h, "panel.Project").center())); + h.step(); + h.input_mut().dropped_files.push(Arc::new(Dropped(std::env::temp_dir().join("Layers.PSD")))); + h.run_steps(2); + assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::Form), "the drop asks first"); + h.run_steps(2); + assert!(h.query_by_label("Import Photoshop File").is_some()); + assert!(h.query_by_value("Composition - Retain Layer Sizes").is_some(), "Import Kind starts at Default Drag Import As"); + assert!(!h.state().session.project.items.values().any(|i| i.name == "Layers.PSD"), "nothing imported before OK"); + click_id(&mut h, "form.cancel"); + assert_eq!(h.state().dialog, None); + + // With Default Drag Import As at Footage, OK imports the file as one item. + h.state_mut().session.prefs.import.drag_import_as = "footage".into(); + h.input_mut().dropped_files.push(Arc::new(Dropped(std::env::temp_dir().join("Layers.PSD")))); + h.run_steps(4); + assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::Form)); + assert!(h.query_by_value("Footage").is_some()); + click_id(&mut h, "form.ok"); + step_until(&mut h, |h| h.state().session.project.items.values().any(|i| i.name == "Layers.PSD")); +} + #[test] fn an_unsupported_drop_reports_the_error_and_keeps_valid_files() { let (mut h, _) = harness();