Plug-ins: a painted fill or stroke effect keeps a placed gradient on its moved shape

A Transform or Free Distort before a painted effect on a fill or stroke moved the shape but left the placed gradient at its old position, both on pieces that keep the fill or stroke and on the geometry drawn after a refused result. The inherited fill or stroke now takes the same gradient move as the Transform branch.
This commit is contained in:
dotCooCoo
2026-11-08 11:05:27 -08:00
parent 64817c2e8c
commit 2f72a998b9
2 changed files with 40 additions and 0 deletions
+5
View File
@@ -1082,6 +1082,11 @@ fn transform_item_leaf(leaf: &mut Node, max: Budget, used: &mut Budget, doc: Opt
let mut it = item.clone();
let rest: Vec<Effect> = before.iter().filter(|e| !is_geometry(&e.id)).chain(&fx[at + 1..]).cloned().collect();
*it.effects_mut() = rest;
let mut one = Appearance { items: vec![it.clone()], ..Default::default() };
if carry.moves(&one) {
carry.apply(&mut one);
}
let it = one.items.pop().unwrap_or(it);
let paints = vectorcraft_plugins::effect::Paints { appearance: Some(&original.appearance), item: Some(i) };
let painted = vectorcraft_plugins::effect::apply_painted(plugin, &params, &g, tb, rule, paints, doc)
.map(|pieces| pieces.iter().map(|p| (p.path.clone(), p.rule, vectorcraft_plugins::painted::items(&it, p))).collect::<Vec<_>>())
+35
View File
@@ -574,6 +574,41 @@ fn a_second_painted_effect_on_a_fill_keeps_the_piece_s_other_fills() {
assert!(paints.contains(&vec![fill("#0000ff"), fill("#00ff00")]), "{paints:?}");
}
/// A Transform effect before a painted effect on a fill moves the fill's placed gradient: a piece
/// that keeps the fill, and the fill drawn on the moved shape after a refused result, both have the
/// gradient at its moved position.
#[test]
fn a_painted_effect_on_a_fill_keeps_a_placed_gradient_with_its_moved_shape() {
let mut gone = solid("#00ff00");
gone["swatch"] = json!("Not a swatch");
for (name, out, want) in [
("keeps", json!({"objects": [{"path": square_at(40.0)}]}).to_string(), square_at(40.0).bounds()),
("refused", json!({"objects": [{"path": square_at(40.0), "fills": [gone]}]}).to_string(), square_at(110.0).bounds()),
] {
let mut s = session();
let id = NodeId(run(&mut s, "shape.rectangle", json!({"x": 10, "y": 40, "width": 20, "height": 20}))["id"].as_u64().unwrap());
run(&mut s, "paint.setStroke", json!({"ids": [id.0], "none": true}));
let stops = json!([{"offset": 0, "color": "#ffffff"}, {"offset": 1, "color": "#000000"}]);
run(&mut s, "paint.setFill", json!({"ids": [id.0], "gradient": {"kind": "linear", "stops": stops}}));
run(&mut s, "select.set", json!({"ids": [id.0]}));
run(&mut s, "paint.setGradientGeom", json!({"start": [14, 50], "end": [26, 50], "stroke": false}));
let plugin = format!("test.engine.carry{name}");
install_wat(&mut s, &tpl::returning(&painting(&plugin, true), &out));
let fill = node(&s, id).appearance.items.iter().position(|i| i.is_fill()).unwrap();
run(&mut s, "effect.apply", json!({"ids": [id.0], "effect": "distort.transform", "params": {"moveH": 100}, "item": fill}));
run(&mut s, "effect.apply", json!({"ids": [id.0], "effect": format!("plugin.{plugin}"), "item": fill}));
let st = s.doc().unwrap();
let art = vectorcraft_render::effects::reshape(Some(&st.doc), st.doc.node(id).unwrap(), None).unwrap();
let mut seen = vec![];
art.walk(&mut |n| {
if let (Some(p), Some(Paint::Gradient(g))) = (n.path_data(), n.appearance.items.iter().find(|i| i.is_fill()).map(|i| i.paint())) {
seen.push((p.bounds(), g.geom.map(|g| (g.start.x, g.end.x))));
}
});
assert_eq!(seen, [(want, Some((114.0, 126.0)))], "{name}");
}
}
/// The geometry of a painted result that names a swatch counts only when the document has that
/// swatch: without a document, and with one that lacks it, the effect keeps its input.
#[test]