From 8eab33eba118dbb7f20e9f9a1dbdba0aff12dcd7 Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Fri, 23 Oct 2026 16:54:21 +0200 Subject: [PATCH] Align: no key object once the selection is down to one object (#1637) --- crates/engine/src/cmd/edit.rs | 3 ++- crates/engine/src/cmd/object.rs | 6 ++--- crates/engine/src/tests.rs | 30 +++++++++++++++++++++++++ crates/ui-egui/src/panels/properties.rs | 2 +- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 2b89d24f..2daa24d5 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -262,7 +262,8 @@ pub fn specs() -> Vec { st.selection.cells = None; if let Some(i) = st.selection.items.iter().position(|x| *x == id) { st.selection.items.remove(i); - if st.selection.key == Some(id) { + // A key object needs another object to align to it. + if st.selection.key == Some(id) || st.selection.items.len() < 2 { st.selection.key = None; } } else { diff --git a/crates/engine/src/cmd/object.rs b/crates/engine/src/cmd/object.rs index f0606e37..65f12062 100644 --- a/crates/engine/src/cmd/object.rs +++ b/crates/engine/src/cmd/object.rs @@ -3074,9 +3074,9 @@ fn align_reference(d: &Document, ids: &[ItemId], to: &str, key: Option) fn align(s: &mut Session, p: &Value) -> Result { let ids = targets(s, p)?; let edge = str_param(p, "edge").unwrap_or("left").to_string(); - // A key object counts only while it is one of the items aligned. With one, InDesign's Align - // To switches to Key Object, so it is also the default target. - let key = s.doc()?.selection.key.filter(|k| ids.contains(k)); + // A key object counts only while it is one of several items aligned. With one, InDesign's + // Align To switches to Key Object, so it is also the default target. + let key = s.doc()?.selection.key.filter(|k| ids.len() > 1 && ids.contains(k)); let to = str_param(p, "to").map(str::to_string).unwrap_or_else(|| { if key.is_some() { "keyObject".into() diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index ff5eb719..43bc767c 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1712,3 +1712,33 @@ fn deselecting_the_key_object_drops_the_key() { s.execute("selection.toggle", &json!({"id": b})).unwrap(); assert_eq!(s.doc().unwrap().selection.key, None); } + +/// With the selection down to the key object alone, Align no longer has a key object: a single +/// object aligns to the page, as before. +#[test] +fn a_lone_key_object_aligns_to_the_page() { + let mut s = session(); + let a = s.execute("frame.create", &json!({"rect": [50.0, 100.0, 100.0, 150.0]})).unwrap()["id"].as_u64().unwrap(); + let b = s.execute("frame.create", &json!({"rect": [200.0, 120.0, 300.0, 170.0]})).unwrap()["id"].as_u64().unwrap(); + s.execute("selection.set", &json!({"ids": [a, b]})).unwrap(); + s.execute("selection.key", &json!({"id": b})).unwrap(); + s.execute("selection.toggle", &json!({"id": a})).unwrap(); + assert_eq!(s.doc().unwrap().selection.key, None); + s.execute("object.align", &json!({"edge": "left"})).unwrap(); + let x0 = s.doc().unwrap().doc.item(designcraft_doc::ItemId(b)).unwrap().bounds().x0; + assert!(x0.abs() < 1e-6, "the object is at {x0}, not at the page's left edge"); +} + +/// Aligning one object alone (`ids`) while it is the key object of a larger selection also takes +/// the page. +#[test] +fn aligning_the_key_object_alone_takes_the_page() { + let mut s = session(); + let a = s.execute("frame.create", &json!({"rect": [50.0, 100.0, 100.0, 150.0]})).unwrap()["id"].as_u64().unwrap(); + let b = s.execute("frame.create", &json!({"rect": [200.0, 120.0, 300.0, 170.0]})).unwrap()["id"].as_u64().unwrap(); + s.execute("selection.set", &json!({"ids": [a, b]})).unwrap(); + s.execute("selection.key", &json!({"id": b})).unwrap(); + s.execute("object.align", &json!({"edge": "left", "ids": [b]})).unwrap(); + let x0 = s.doc().unwrap().doc.item(designcraft_doc::ItemId(b)).unwrap().bounds().x0; + assert!(x0.abs() < 1e-6, "the object is at {x0}, not at the page's left edge"); +} diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index a31cfe4e..0422936e 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -983,7 +983,7 @@ fn align_row(app: &mut DesignApp, ui: &mut Ui) { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 8.0; // A key object switches Align To to Key Object; choosing another target drops the key. - let key = app.session.active().and_then(|st| st.selection.key); + let key = app.session.active().and_then(|st| st.selection.key.filter(|_| st.selection.items.len() > 1)); let to = if key.is_some() { "keyObject".to_string() } else { app.ui.align_to.clone() }; let to_label = match to.as_str() { "selection" => "Selection",