From 424a49604cc3131a4c5bab9d32bcca257e957580 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 17 Oct 2026 12:49:08 +0700 Subject: [PATCH] Layers: Merge Selected keeps to the highlighted layers, as documented (#2383) Reverts the fallback that merged the layers of art selected on the canvas when no layer row is highlighted: Illustrator's Merge Selected merges the highlighted rows into the last one highlighted. --- crates/engine/src/cmd/layerpanel.rs | 13 ++----------- crates/engine/src/tests_layers.rs | 18 ------------------ crates/ui-egui/src/panels/layers.rs | 4 +--- 3 files changed, 3 insertions(+), 32 deletions(-) diff --git a/crates/engine/src/cmd/layerpanel.rs b/crates/engine/src/cmd/layerpanel.rs index 66cf37ff7..c302a7409 100644 --- a/crates/engine/src/cmd/layerpanel.rs +++ b/crates/engine/src/cmd/layerpanel.rs @@ -263,22 +263,13 @@ fn merge_into(d: &mut Document, into: NodeId, others: &[NodeId]) -> Result<()> { fn merge(s: &mut Session, p: &Value) -> Result { const C: &str = "layer.merge"; - let mut ids = rows_param(s, p, C)?; + let ids = rows_param(s, p, C)?; let st = s.doc()?; let d = &st.doc; - // Nothing named or highlighted, but art selected on the canvas in several layers (Select All): - // merge those layers (#2383). - if p.get("ids").is_none() && p.get("id").is_none() && st.highlighted_rows().is_empty() { - let held: HashSet = st.selection.objects.iter().filter_map(|o| d.layer_of(*o)).collect(); - let layers: Vec = d.layers.iter().map(|l| l.id).filter(|l| held.contains(l)).collect(); - if layers.len() >= 2 { - ids = layers; - } - } let mut seen = HashSet::new(); let ids: Vec = ids.into_iter().filter(|id| d.node(*id).is_some() && seen.insert(*id)).collect(); if ids.len() < 2 { - return Err(bad(C, "highlight two or more rows to merge, or select art in two or more layers")); + return Err(bad(C, "highlight two or more rows to merge")); } // The last highlighted layer that is not inside another highlighted row: with a layer and its // sublayer both highlighted (Select All in the panel), the sublayer travels with its layer diff --git a/crates/engine/src/tests_layers.rs b/crates/engine/src/tests_layers.rs index 7591873d2..1fc789c80 100644 --- a/crates/engine/src/tests_layers.rs +++ b/crates/engine/src/tests_layers.rs @@ -625,24 +625,6 @@ fn merge_selected_with_all_layer_rows_highlighted_merges_into_the_last_top_level } } -/// Merge Selected with no highlighted rows but art selected in several layers (Select All on the -/// canvas) merges those layers; art in one layer still asks for rows (#2383). -#[test] -fn merge_selected_with_art_selected_in_several_layers_merges_those_layers() { - let mut s = session(); - let a = rect(&mut s, 0.0); - run(&mut s, "layer.new", json!({"top": true})); - let b = rect(&mut s, 100.0); - run(&mut s, "select.set", json!({"ids": [b.0]})); - run(&mut s, "layer.highlight", json!({"ids": []})); - assert!(s.execute("layer.merge", &json!({})).is_err()); - run(&mut s, "select.set", json!({"ids": [a.0, b.0]})); - let into = id(&run(&mut s, "layer.merge", json!({}))); - let st = s.doc().unwrap(); - assert_eq!(st.doc.layers.len(), 1); - assert!(st.doc.layer_of(a) == Some(into) && st.doc.layer_of(b) == Some(into)); -} - /// Collect in New Layer puts the new layer beside the topmost row when that row is directly in a /// layer, and on top of the layer holding its group when it is in a group. When that row is in a /// hidden or locked layer, the selected art it collects from another layer is deselected, the key diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 0642274ea..7fe651ced 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -1461,8 +1461,6 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { let rows = st.highlighted_rows(); let current = st.current_layer(); let has_sel = !st.selection.is_empty(); - // How many top-level layers hold the selected art: Merge Selected merges them. - let art_layers = st.selection.objects.iter().filter_map(|o| doc.layer_of(*o)).collect::>().len(); let isolated = st.isolation.is_some(); let targets: Vec = if rows.is_empty() { current.into_iter().collect() } else { rows.clone() }; let one_container = match targets.as_slice() { @@ -1500,7 +1498,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { None, Some(("Locate Object", "layer.locate", json!({}), has_sel, false)), None, - Some(("Merge Selected", "layer.merge", json!({}), rows.len() >= 2 || (rows.is_empty() && art_layers >= 2), false)), + Some(("Merge Selected", "layer.merge", json!({}), rows.len() >= 2, false)), Some(("Flatten Artwork", "layer.flatten", json!({}), true, false)), Some(("Collect in New Layer", "layer.collectInNew", json!({}), !rows.is_empty() || has_sel, false)), // Sentinel: rendered as Collect for Export › As Single / As Multiple (assets.add).