Pathfinder panel: a group selected alone counts its nested shapes, so an expanded stroke keeps the Shape Modes enabled (fixes #1635)

This commit is contained in:
Marco Arnulfo authored and bflatastic committed 2026-10-14 02:09:07 +07:00
1 parent 359d9bb833
commit 7ea75b01ba
3 files changed
+45 -4

No files matched your search

+3
View File
@@ -493,6 +493,9 @@ pub use flatten::FlattenOptions;
/// A built-in or saved flattener preset ([`crate::Prefs::flattener_presets`]).
pub use flatten::FlattenerPreset;
/// The shapes a group selected alone gives the Pathfinder panel.
pub use pathops::shape_count;
/// Command ids kept only so older scripts keep working: `(alias, the command it duplicates)`.
/// They run as before but the command palette leaves them out (it lists the command once).
pub const ALIASES: &[(&str, &str)] = &[("object.convertDocumentColorMode", "file.documentColorMode")];
+8
View File
@@ -258,6 +258,14 @@ fn leaves(n: &Node, out: &mut Vec<Node>) {
}
}
/// How many shapes a Pathfinder operation takes from `n` selected alone: its leaf shapes, however
/// deep its groups nest (an expanded stroke is a group inside the group, #1635).
pub fn shape_count(n: &Node) -> usize {
let mut lv = vec![];
leaves(n, &mut lv);
lv.len()
}
/// A node for `path`: a plain path for one subpath, else a compound path.
pub(crate) fn shape_node(d: &mut Document, path: PathData, style_src: Option<&Node>) -> Node {
let id = d.alloc_id();
+34 -4
View File
@@ -29,12 +29,12 @@ fn run(app: &mut VectorcraftApp, ui: &Ui, op: &str, label: &str) {
}
}
/// How many objects the selection gives an operation: a group selected alone counts as its
/// members (#1450).
/// How many objects the selection gives an operation: a group selected alone counts as the shapes
/// the operation takes from it, nested groups included (#1450, #1635).
fn operands(app: &VectorcraftApp) -> usize {
let alone = app.session.active().filter(|d| d.selection.len() == 1).and_then(|d| d.doc.node(*d.selection.objects.first()?));
match alone.and_then(|n| n.children().filter(|_| matches!(n.kind, vectorcraft_doc::NodeKind::Group { .. }))) {
Some(members) => members.len(),
match alone.filter(|n| matches!(n.kind, vectorcraft_doc::NodeKind::Group { .. })) {
Some(group) => vectorcraft_engine::cmd::shape_count(group),
None => selection_len(app),
}
}
@@ -96,3 +96,33 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
menu_item(ui, tl!("Release Compound Shape"), false, false);
menu_item(ui, tl!("Expand Compound Shape"), false, false);
}
#[cfg(test)]
mod tests {
use super::*;
use vectorcraft_engine::doc::{NodeId, NodeKind};
/// #1635: a stroked object expanded inside a group leaves a group holding one group (its fill
/// and its outlined stroke). The Shape Modes take the group's shapes, so they stay enabled, and
/// Unite merges the fill and the stroke into one path.
#[test]
fn shape_modes_take_the_shapes_of_an_expanded_stroke_in_a_group() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
app.run("stroke.set", json!({"weight": 10})).unwrap();
app.run("object.group", json!({})).unwrap();
app.run("object.expand", json!({})).unwrap();
let doc = &app.session.active().unwrap().doc;
let group = doc.node(*app.session.active().unwrap().selection.objects.first().unwrap()).unwrap();
let members = group.children().unwrap();
assert_eq!(members.len(), 1, "one member: the expanded rectangle");
assert!(matches!(members[0].kind, NodeKind::Group { .. }), "the fill and the outlined stroke, grouped");
assert_eq!(operands(&app), 2, "the fill and the outlined stroke");
let id = app.run("object.pathfinder.unite", json!({})).unwrap()["ids"][0].as_u64().unwrap();
let united = app.session.active().unwrap().doc.node(NodeId(id)).unwrap();
assert!(matches!(united.kind, NodeKind::Path { .. }), "one path, the stroke's inner edge gone: {:?}", united.kind);
let b = united.geometric_bounds().unwrap();
assert_eq!((b.x0, b.y0, b.x1, b.y1), (5.0, 5.0, 65.0, 65.0), "the stroke's outer edge");
}
}