Preferences: Skip Transform when Placing (#8492)

Preferences > General > Skip Transform when Placing (off by default), as in
Photoshop: a placed or dropped file lands without Free Transform. It is
checked in begin_placed, which every interactive place reaches, so drops
still go through main's background place job (#10147) and queue; with the
preference on, the next queued file starts as soon as one lands.
Scripted file.placeEmbedded/placeLinked never open Free Transform, so the
preference doesn't affect them. Label translated in every catalog that
translates the neighbouring place preferences (all but hi).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cristi RaduandClaude Opus 5.5 committed 2026-11-06 20:38:19 +02:00
1 parent 2ff8f419cb
commit 53917dc175
25 files changed
+114 -2

No files matched your search

+5
View File
@@ -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,
+66
View File
@@ -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 {
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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 με επίπεδα
Can't render this file because it contains an unexpected character in line 47 and column 53.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 4 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 79 and column 53.
+1
View File
@@ -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 確認する
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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 묻기
Can't render this file because it contains an unexpected character in line 63 and column 53.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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 Спрашивать
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 8 and column 34.
+1
View File
@@ -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 Запитувати
Can't render this file because it contains an unexpected character in line 152 and column 53.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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 询问
Can't render this file because it contains an unexpected character in line 63 and column 53.
+1
View File
@@ -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 詢問
Can't render this file because it contains an unexpected character in line 6 and column 47.
@@ -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);
@@ -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();
+2 -2
View File
@@ -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`.