diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index ee61af791c..e705bc7142 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -227,6 +227,10 @@ pub struct General { pub export_clipboard: bool, pub resize_image_during_place: bool, pub always_create_smart_objects_when_placing: bool, + /// Place files without opening Free Transform on them (#8492). Only the interactive place + /// (File ▸ Place, canvas drops) opens Free Transform; `file.placeEmbedded`/`file.placeLinked` + /// run with a `path` (actions, scripts, automation) never do, so this doesn't affect them. + pub skip_transform_when_placing: bool, /// Scroll wheel zooms instead of panning. pub zoom_with_scroll_wheel: bool, /// Wheel zoom eases over the frames of a notch; off, a notch is one instant ×1.1 step. @@ -247,6 +251,7 @@ impl Default for General { export_clipboard: true, resize_image_during_place: true, always_create_smart_objects_when_placing: true, + skip_transform_when_placing: false, zoom_with_scroll_wheel: false, animated_zoom: true, zoom_resizes_windows: false, diff --git a/crates/ui-egui/src/file_open/tests.rs b/crates/ui-egui/src/file_open/tests.rs index c1b5ccde35..a617332bd1 100644 --- a/crates/ui-egui/src/file_open/tests.rs +++ b/crates/ui-egui/src/file_open/tests.rs @@ -739,6 +739,72 @@ fn cancelling_a_background_place_continues_the_queue() { assert_eq!(layer_count(&app), layers + 1); } +/// #8492: with Skip Transform when Placing on, dropped files still go through the background place +/// job one at a time, but no Free Transform opens when a layer lands: the next file starts as soon +/// as the previous one has landed, without waiting for ↩. +#[test] +fn skip_transform_when_placing_places_background_drops_without_free_transform() { + let ctx = egui::Context::default(); + let mut app = app_with_canvas(); + app.background_jobs = true; + app.session.execute("prefs.set", json!({"path": "general.skipTransformWhenPlacing", "value": true})).unwrap(); + let (layers, steps) = (layer_count(&app), history_steps(&app)); + app.open_dropped(&ctx, vec![dropped("first.png", Ok(png_bytes(4, 4))), dropped("second.png", Ok(png_bytes(2, 2)))], Some(egui::pos2(400.0, 300.0))); + app.place_next_dropped(&ctx); + let first = app.jobs.place.clone().expect("the first file is being placed"); + assert_eq!(first.name, "first.png"); + assert!(app.session.job(first.job).is_some(), "as a background job"); + assert_eq!(app.drop_places.len(), 1, "the second file waits for the first job"); + tick_until(&mut app, &ctx, |app| app.jobs.place.as_ref().is_some_and(|p| p.name == "second.png")); + assert!(app.ui.transform.is_none(), "no Free Transform on the first file"); + assert_eq!(layer_count(&app), layers + 1); + tick_until(&mut app, &ctx, |app| app.jobs.place.is_none()); + assert!(app.ui.transform.is_none() && app.drop_places.is_empty()); + let st = app.session.active().unwrap(); + assert_eq!(st.doc.layer(st.active_layer.unwrap()).unwrap().name, "second"); + assert_eq!((layer_count(&app), history_steps(&app)), (layers + 2, steps + 2), "one Place Embedded step each"); + assert_eq!(app.session.active().unwrap().history.undo_label(), Some("Place Embedded")); +} + +/// #8492: inline (background jobs off, as on the web) the preference places every queued drop +/// without Free Transform, a file that can't be placed reported on the way. +#[test] +fn skip_transform_when_placing_places_inline_drops_without_free_transform() { + let ctx = egui::Context::default(); + let mut app = app_with_canvas(); + app.session.execute("prefs.set", json!({"path": "general.skipTransformWhenPlacing", "value": true})).unwrap(); + let (layers, steps) = (layer_count(&app), history_steps(&app)); + app.open_dropped( + &ctx, + vec![dropped("first.png", Ok(png_bytes(4, 4))), dropped("bad.png", Ok(b"bad".to_vec())), dropped("second.png", Ok(png_bytes(2, 2)))], + Some(egui::pos2(400.0, 300.0)), + ); + app.place_next_dropped(&ctx); + assert!(app.ui.transform.is_none() && app.drop_places.is_empty()); + assert_eq!((layer_count(&app), history_steps(&app)), (layers + 2, steps + 2)); + assert_eq!(app.ui.notices.iter().filter(|n| n.error && n.title.contains("bad.png")).count(), 1); +} + +/// #8492: Skip Transform when Placing only concerns the interactive place. `file.placeEmbedded` +/// with a `path` (actions, scripts, the control channel, MCP) runs the engine command, which never +/// opens Free Transform, whatever the preference. +#[test] +fn a_scripted_place_never_opens_free_transform() { + let dir = std::env::temp_dir().join(format!("pc-scripted-place-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("dot.png"); + std::fs::write(&path, png_bytes(4, 3)).unwrap(); + let mut app = app_with_canvas(); + for skip in [false, true] { + app.session.execute("prefs.set", json!({"path": "general.skipTransformWhenPlacing", "value": skip})).unwrap(); + let layers = layer_count(&app); + app.run("file.placeEmbedded", json!({"path": path.to_string_lossy()})).unwrap(); + assert_eq!(layer_count(&app), layers + 1, "skip={skip}"); + assert!(app.ui.transform.is_none(), "skip={skip}"); + } + std::fs::remove_dir_all(dir).unwrap(); +} + /// Frames with nothing to wait for: jobs applied and the drop queue served. fn idle_frames(app: &mut PhotocraftApp, ctx: &egui::Context) { for _ in 0..3 { diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index d82218a2b9..2c0e40790e 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2029,6 +2029,7 @@ Allow scripts to connect Povolit připojení skriptů Always Vždy Always create smart objects when placing Při umísťování vždy vytvořit chytré objekty + Skip transform when placing Při umísťování přeskočit transformaci Animated zoom Animované zvětšování Apply auto tone Použít automatický tón Ask Zeptat se diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 6bfdefb09d..172c1131d9 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1990,6 +1990,7 @@ Allow scripts to connect Skriptverbindungen erlauben Always Immer Always create smart objects when placing Beim Platzieren immer Smart-Objekte erstellen + Skip transform when placing Transformieren beim Platzieren überspringen Animated zoom Animierter Zoom Apply auto tone Automatische Tonwertkorrektur anwenden Ask Nachfragen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 3a76328295..0962d4887e 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1750,6 +1750,7 @@ Allow agent control Να επιτρέπεται ο έλεγχος από πράκτορες Allow scripts to connect Να επιτρέπεται η σύνδεση σεναρίων Always create smart objects when placing Να δημιουργούνται πάντα έξυπνα αντικείμενα κατά την τοποθέτηση + Skip transform when placing Παράλειψη μετασχηματισμού κατά την τοποθέτηση Animated zoom Κινούμενη μεγέθυνση Apply auto tone Εφαρμογή αυτόματης τονικής διόρθωσης Ask before saving layered tiff Ερώτηση πριν από την αποθήκευση TIFF με επίπεδα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index fd166bb321..f6a6d8ff87 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1960,6 +1960,7 @@ Allow scripts to connect Permitir que las secuencias de comandos se conecten Always Siempre Always create smart objects when placing Crear siempre objetos inteligentes al colocar + Skip transform when placing Omitir la transformación al colocar Animated zoom Zoom animado Apply auto tone Aplicar el tono automático Ask Preguntar diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 0b4394288d..d7c3ea1b49 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1966,6 +1966,7 @@ Allow scripts to connect Autoriser la connexion des scripts Always Toujours Always create smart objects when placing Toujours créer des objets intelligents lors du placement + Skip transform when placing Ignorer la transformation lors du placement Animated zoom Zoom animé Apply auto tone Appliquer la tonalité automatique Ask Demander diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index bb919700eb..fe4cddfed6 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2724,6 +2724,7 @@ cameraRaw Yellow Sárga All Slices Összes szelet All User Slices Összes felhasználói szelet Always create smart objects when placing Elhelyezéskor mindig intelligens objektum létrehozása + Skip transform when placing Alakítás kihagyása elhelyezéskor Answer the unsaved-changes prompt first Előbb válaszolj a nem mentett módosításokra vonatkozó kérdésre Appearance Settings… Megjelenési beállítások… Apple Watch 45mm Apple Watch 45 mm diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index c62378e593..db466320d6 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1977,6 +1977,7 @@ Allow scripts to connect Izinkan skrip terhubung Always Selalu Always create smart objects when placing Selalu buat smart object saat menempatkan + Skip transform when placing Lewati transformasi saat menempatkan Animated zoom Zoom beranimasi Apply auto tone Terapkan auto tone Ask Tanya diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 384e18f2b8..267d935bd7 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2005,6 +2005,7 @@ Allow scripts to connect Consenti connessione script Always Sempre Always create smart objects when placing Crea sempre oggetti avanzati all'inserimento + Skip transform when placing Salta la trasformazione all'inserimento Animated zoom Zoom animato Apply auto tone Applica tono automatico Ask Chiedi diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 06f61dcdd0..093adc0b8d 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2016,6 +2016,7 @@ Allow scripts to connect スクリプトからの接続を許可 Always 常に Always create smart objects when placing 配置時に常にスマートオブジェクトを作成 + Skip transform when placing 配置時に変形をスキップ Animated zoom アニメーションズーム Apply auto tone 自動トーン補正を適用 Ask 確認する diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index c1bd0d2ca0..f23fb24916 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1989,6 +1989,7 @@ Allow scripts to connect 스크립트 연결 허용 Always 항상 Always create smart objects when placing 가져올 때 항상 스마트 오브젝트 만들기 + Skip transform when placing 가져올 때 변형 건너뛰기 Animated zoom 확대/축소 애니메이션 Apply auto tone 자동 톤 적용 Ask 묻기 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 246214818e..4e6813d9b0 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1937,6 +1937,7 @@ Allow scripts to connect Scripts toestaan verbinding te maken Always Altijd Always create smart objects when placing Bij plaatsen altijd slimme objecten maken + Skip transform when placing Transformatie overslaan bij plaatsen Animated zoom Geanimeerd zoomen Apply auto tone Automatische tooncorrectie toepassen Ask Vragen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index e40dece48a..435336807f 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2002,6 +2002,7 @@ Allow scripts to connect Zezwalaj skryptom na połączenie Always Zawsze Always create smart objects when placing Zawsze twórz obiekty inteligentne podczas umieszczania + Skip transform when placing Pomijaj przekształcanie podczas umieszczania Animated zoom Animowane powiększanie Apply auto tone Zastosuj automatyczne tony Ask Pytaj diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 122ab72265..0783a81269 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1960,6 +1960,7 @@ Allow scripts to connect Permitir conexão de scripts Always Sempre Always create smart objects when placing Sempre criar objetos inteligentes ao inserir + Skip transform when placing Ignorar transformação ao inserir Animated zoom Zoom animado Apply auto tone Aplicar tom automático Ask Perguntar diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index e66c3f98f3..5971fb35c3 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2419,6 +2419,7 @@ Allow scripts to connect Разрешить подключение сценариев Always Всегда Always create smart objects when placing Всегда создавать смарт-объекты при помещении + Skip transform when placing Пропускать трансформирование при помещении Animated zoom Анимированное масштабирование Apply auto tone Применять автотон Ask Спрашивать diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index 3e12107087..69a3317a36 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -1976,6 +1976,7 @@ Allow scripts to connect Dozvoli skriptama da se povežu Always Uvek Always create smart objects when placing Uvek pravi pametne objekte pri postavljanju + Skip transform when placing Preskoči transformaciju pri postavljanju Animated zoom Animirano zumiranje Apply auto tone Primeni automatski ton Ask Pitaj diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 6d40778728..226eb85e29 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1890,6 +1890,7 @@ Allow scripts to connect Komut dosyalarının bağlanmasına izin ver Always Her zaman Always create smart objects when placing Yerleştirirken daima akıllı nesneler yaratın + Skip transform when placing Yerleştirirken dönüştürmeyi atla Animated zoom Animasyonlu yakınlaştırma Apply auto tone Otomatik tonu uygula Ask Sor diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index bca4b9619b..b4f7da8bdc 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2385,6 +2385,7 @@ Allow scripts to connect Дозволити підключення скриптів Always Завжди Always create smart objects when placing Завжди створювати смартоб'єкти під час розміщення + Skip transform when placing Пропускати трансформацію під час розміщення Animated zoom Анімоване масштабування Apply auto tone Застосовувати автоматичну тонову корекцію Ask Запитувати diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index a2f7bd141f..16fbc6ad74 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2756,6 +2756,7 @@ shape Appearance Diện mạo Allow agent control Cho phép agent điều khiển Allow scripts to connect Cho phép script kết nối Always create smart objects when placing Luôn tạo đối tượng thông minh khi đặt + Skip transform when placing Bỏ qua biến đổi khi đặt Animated zoom Thu phóng có chuyển động Apply auto tone Áp dụng tự động tông Auto collapse icon panels Tự động thu gọn các bảng biểu tượng diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d8493b38b1..ed44494c44 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2042,6 +2042,7 @@ Allow scripts to connect 允许脚本连接 Always 始终 Always create smart objects when placing 置入时始终创建智能对象 + Skip transform when placing 置入时跳过变换 Animated zoom 动画缩放 Apply auto tone 应用自动色调 Ask 询问 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 170787a915..8e92f3014a 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2010,6 +2010,7 @@ Allow scripts to connect 允許指令碼連線 Always 一律 Always create smart objects when placing 置入時一律建立智慧型物件 + Skip transform when placing 置入時略過變形 Animated zoom 動畫縮放 Apply auto tone 套用自動色調 Ask 詢問 diff --git a/crates/ui-egui/src/tools/transform_tool/begin.rs b/crates/ui-egui/src/tools/transform_tool/begin.rs index 3ab5f513a1..87802c44eb 100644 --- a/crates/ui-egui/src/tools/transform_tool/begin.rs +++ b/crates/ui-egui/src/tools/transform_tool/begin.rs @@ -287,7 +287,11 @@ pub fn begin_copy(app: &mut PhotocraftApp, ctx: &egui::Context) -> Result<(), St /// Free Transform on the layer a file dropped on the canvas just placed, like the reference app's /// Place: Esc takes the place back, ↩ makes the place and the transform one Place Embedded step. +/// With Preferences ▸ General ▸ Skip Transform when Placing on, the place stands as it is. pub fn begin_placed(app: &mut PhotocraftApp, ctx: &egui::Context) -> Result<(), String> { + if app.session.prefs().general.skip_transform_when_placing { + return Ok(()); + } begin(app, ctx)?; if let Some(t) = app.ui.transform.as_mut() { t.made = Some(MadeLayer::Place); diff --git a/crates/ui-egui/src/tools/transform_tool/tests.rs b/crates/ui-egui/src/tools/transform_tool/tests.rs index 5d5507bfd8..3721fdeb2a 100644 --- a/crates/ui-egui/src/tools/transform_tool/tests.rs +++ b/crates/ui-egui/src/tools/transform_tool/tests.rs @@ -554,6 +554,23 @@ fn editing_the_contents_of_a_dropped_file_keeps_the_layer() { assert!(app.transform_preview.is_none(), "the transform preview should end"); } +/// #8492: Preferences ▸ General ▸ Skip Transform when Placing (off by default) places a file +/// without opening Free Transform on it, as in the reference app. +#[test] +fn skip_transform_when_placing_leaves_the_place_without_a_box() { + for skip in [false, true] { + let mut app = app_with_square(64, photocraft_geom::Rect::new(8, 8, 24, 24)); + app.session.execute("prefs.set", json!({"path": "general.skipTransformWhenPlacing", "value": skip})).unwrap(); + app.session.execute("layer.smartObjects.convertToSmartObject", json!({})).unwrap(); + app.session.execute("layer.duplicate", json!({"inPlace": true})).unwrap(); + app.sync_views(); + let placed = app.session.active().unwrap().active_layer.unwrap(); + begin_placed(&mut app, &egui::Context::default()).unwrap(); + assert_eq!(app.ui.transform.is_some(), !skip, "skip {skip}: Free Transform opens only with the preference off"); + assert!(app.session.active().unwrap().doc.layer(placed).is_some(), "skip {skip}: the placed layer stays"); + } +} + /// Polls the app's background jobs until they have all landed. fn land_jobs(app: &mut PhotocraftApp) { let ctx = egui::Context::default(); diff --git a/docs/scorecard.md b/docs/scorecard.md index a3ecd984cb..a463764146 100644 --- a/docs/scorecard.md +++ b/docs/scorecard.md @@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js | [Performance](#performance) | 3 | 22 | 51 | 85 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 47 not run, 4 not measurable yet (baseline 2026-10-05) | | [File compatibility](#file-compatibility) | 11 | 14 | 2 | io corpus: oracle ≥ 148/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 249/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 190/258, round trip ≥ 258/258 | | [Tools](#tools) | 36 | 9 | 0 | | -| [Workspace and UI](#workspace-and-ui) | 15 | 6 | 2 | settings that do nothing: 10 of 192 (target 0) | +| [Workspace and UI](#workspace-and-ui) | 15 | 6 | 2 | settings that do nothing: 10 of 193 (target 0) | | [Type](#type) | 5 | 5 | 0 | | | [Automation](#automation) | 5 | 3 | 3 | 660 of 660 menu items live (100.0%) | | [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 30 of 30 crates; panic_hunt present | @@ -209,7 +209,7 @@ Corpus floors (`crates/io/tests/it/corpus.rs`): the enforced minimums; the corpu ## Workspace and UI -**Settings that do nothing: 10 of 192** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. +**Settings that do nothing: 10 of 193** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't. Unread: `general.color_picker`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `performance.legacy_compositing`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `type_.text_engine`, `enhanced_controls.scrubby_slider_acceleration`.