mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 23:31:23 +00:00
Merge pull request #6222 from dotCooCoo/fix/swatch-new-gradient-link
Swatches: a new swatch holds no link or placement from the paint it is made from
This commit is contained in:
@@ -19,7 +19,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"New Swatch",
|
||||
["Window", "Swatches"],
|
||||
None,
|
||||
"{name?, color? | colors?: [colour] (one swatch each, in one undo step; #rrggbbaa and #rgba are refused) | swatch? | gradient? | pattern?: name (default: the current fill), tint?: 0..100 (% of the global or spot `swatch`, or of the one the current fill links to), mode?: \"gray\"|\"rgb\"|\"hsb\"|\"lab\"|\"cmyk\"|\"web\" (convert the colour; lab: CIE L*a*b*, how spot colours are usually defined), global?, spot? (a spot colour, always global), group?: colour group name (solid colours only; default: ungrouped), none?: true (the None paint), paint?: paint.setFill params, keepModel?: bool (as paint.setFill)} save a colour, gradient or pattern as a swatch. A tint of a global or spot colour (below 100%, without mode, global or spot) becomes a tint swatch, \"Name 40%\", linked to its base: it follows edits to the base and applies as that tint of it. Names are unique (\"Sky 2\"); a colour's default name is its values (\"C=10 M=20 Y=30 K=0\", \"R=255 G=128 B=0\", \"Gray K=40\", \"L=55 a=60 b=40\") → {name, names: [every new swatch]}",
|
||||
"{name?, color? | colors?: [colour] (one swatch each, in one undo step; #rrggbbaa and #rgba are refused) | swatch? | gradient? | pattern?: name (default: the current fill), tint?: 0..100 (% of the global or spot `swatch`, or of the one the current fill links to), mode?: \"gray\"|\"rgb\"|\"hsb\"|\"lab\"|\"cmyk\"|\"web\" (convert the colour; lab: CIE L*a*b*, how spot colours are usually defined), global?, spot? (a spot colour, always global), group?: colour group name (solid colours only; default: ungrouped), none?: true (the None paint), paint?: paint.setFill params, keepModel?: bool (as paint.setFill)} save a colour, gradient or pattern as a swatch. A tint of a global or spot colour (below 100%, without mode, global or spot) becomes a tint swatch, \"Name 40%\", linked to its base: it follows edits to the base and applies as that tint of it. A gradient swatch holds no link to the swatch its gradient came from, and a pattern swatch holds the pattern untransformed. Names are unique (\"Sky 2\"); a colour's default name is its values (\"C=10 M=20 Y=30 K=0\", \"R=255 G=128 B=0\", \"Gray K=40\", \"L=55 a=60 b=40\") → {name, names: [every new swatch]}",
|
||||
has_doc,
|
||||
swatch_new
|
||||
),
|
||||
@@ -187,7 +187,11 @@ fn swatch_new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
})),
|
||||
Paint::None => Err(bad(C, "a swatch needs a colour, gradient or pattern")),
|
||||
_ if spot || mode.is_some() || group.is_some() => Err(bad(C, "spot, mode and group apply to solid colours only")),
|
||||
other => Ok(other),
|
||||
// The swatch holds the gradient itself, not a link to the swatch it may have come from.
|
||||
// It keeps `geom`, which holds the aspect the swatch is applied with.
|
||||
Paint::Gradient(g) => Ok(Paint::Gradient(Box::new(vectorcraft_color::GradientPaint { swatch: None, ..*g }))),
|
||||
// The swatch holds the pattern untransformed, not the placement of the art it came from.
|
||||
Paint::Pattern { pattern, .. } => Ok(pattern_paint(&pattern)),
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
// A name applies to a single new swatch.
|
||||
|
||||
@@ -278,6 +278,54 @@ fn gradient_swatches_come_along_with_their_stop_links() {
|
||||
assert_eq!(sw.gradient, g.gradient, "the added gradient swatch links its stop to the added Sky too");
|
||||
}
|
||||
|
||||
/// A gradient swatch made from another one (by name, from a fill linked to it, or from a gradient
|
||||
/// that names it, placed or not) holds the gradient alone, so copying art that uses it brings only
|
||||
/// that swatch. A placed gradient keeps its aspect, and the swatch still fits each object it is
|
||||
/// applied to.
|
||||
#[test]
|
||||
fn a_gradient_swatch_made_from_another_holds_no_link_to_it() {
|
||||
let mut s = session();
|
||||
let glow = json!({"kind": "radial", "stops": [{"offset": 0, "color": "#3080ff"}, {"offset": 1, "color": "#ffffff"}]});
|
||||
s.execute("swatch.new", &json!({"name": "Glow", "gradient": glow})).unwrap();
|
||||
let r = rect(&mut s, 0.0, 0.0, 50.0, 50.0);
|
||||
s.execute("paint.setFill", &json!({"ids": [r.0], "swatch": "Glow"})).unwrap();
|
||||
let Paint::Gradient(applied) = fill(&s, r) else { panic!("a gradient fill") };
|
||||
assert_eq!(applied.swatch.as_deref(), Some("Glow"));
|
||||
let mut by_gradient = vectorcraft_tools::params::gradient_params(&applied);
|
||||
by_gradient["swatch"] = json!("Glow");
|
||||
let mut placed = by_gradient.clone();
|
||||
placed["start"] = json!([25, 25]);
|
||||
placed["end"] = json!([50, 25]);
|
||||
placed["aspect"] = json!(50);
|
||||
let made = [
|
||||
("By Name", json!({"name": "By Name", "swatch": "Glow"})),
|
||||
("From Fill", json!({"name": "From Fill"})),
|
||||
("From Gradient", json!({"name": "From Gradient", "gradient": by_gradient})),
|
||||
("Placed", json!({"name": "Placed", "gradient": placed})),
|
||||
];
|
||||
let mut links = vec![];
|
||||
for (name, p) in &made {
|
||||
s.execute("swatch.new", p).unwrap();
|
||||
let Paint::Gradient(sw) = doc(&s).swatch(name).unwrap().paint.clone() else { panic!("{name}: a gradient swatch") };
|
||||
assert_eq!(sw.gradient, applied.gradient, "{name}: Glow's gradient");
|
||||
links.push(sw.swatch.clone());
|
||||
}
|
||||
assert_eq!(links, [None, None, None, None], "no swatch links to Glow");
|
||||
let Paint::Gradient(sw) = doc(&s).swatch("Placed").unwrap().paint.clone() else { panic!("a gradient swatch") };
|
||||
assert!(sw.geom.is_some_and(|g| (g.aspect - 0.5).abs() < 1e-9), "the placed gradient keeps its aspect: {:?}", sw.geom);
|
||||
for (name, _) in &made {
|
||||
let t = rect(&mut s, 100.0, 0.0, 100.0, 60.0);
|
||||
s.execute("paint.setFill", &json!({"ids": [t.0], "swatch": name})).unwrap();
|
||||
if *name == "Placed" {
|
||||
let Paint::Gradient(g) = fill(&s, t) else { panic!("a gradient fill") };
|
||||
let geom = g.geom.unwrap();
|
||||
assert!(geom.start.distance(Point::new(150.0, 30.0)) < 1e-9 && (geom.aspect - 0.5).abs() < 1e-9, "{geom:?}");
|
||||
}
|
||||
copy(&mut s, &[t]);
|
||||
assert_eq!(s.clipboard.swatches.iter().map(|w| w.name.as_str()).collect::<Vec<_>>(), [*name], "Glow stays behind");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lab_spot_colours_show_as_the_target_documents_spot_option_says() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -979,6 +979,32 @@ fn picking_another_pattern_swatch_keeps_the_pattern_transform() {
|
||||
assert_eq!(xf.as_coeffs(), [2.0, 0.0, 0.0, 2.0, 15.0, 10.0]);
|
||||
}
|
||||
|
||||
/// A pattern swatch made from the current fill holds the pattern untransformed: a fill picked up
|
||||
/// from art whose tiles were moved does not carry that placement to the art the swatch is applied
|
||||
/// to, which keeps its own (#1970).
|
||||
#[test]
|
||||
fn a_pattern_swatch_made_from_a_transformed_fill_holds_the_pattern_untransformed() {
|
||||
let mut s = session();
|
||||
make_dots(&mut s);
|
||||
let src = rect(&mut s, 100.0, 100.0, 50.0, 50.0);
|
||||
s.execute("paint.setFill", &json!({"ids": [src.0], "swatch": "Dots"})).unwrap();
|
||||
s.execute("pattern.transform", &json!({"ids": [src.0], "matrix": [2, 0, 0, 2, 15, 10]})).unwrap();
|
||||
sel(&mut s, &[]);
|
||||
s.execute("appearance.copyFrom", &json!({"source": src.0})).unwrap();
|
||||
let Paint::Pattern { xf, .. } = &s.paint.fill else { panic!("the picked-up pattern fill") };
|
||||
assert_eq!(xf.as_coeffs(), [2.0, 0.0, 0.0, 2.0, 15.0, 10.0], "the current fill has the tiles' placement");
|
||||
s.execute("swatch.new", &json!({"name": "Picked"})).unwrap();
|
||||
let Paint::Pattern { pattern, xf } = doc(&s).swatch("Picked").unwrap().paint.clone() else { panic!("a pattern swatch") };
|
||||
assert_eq!((pattern.as_str(), xf.as_coeffs()), ("Dots", [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]));
|
||||
let dst = rect(&mut s, 300.0, 100.0, 50.0, 50.0);
|
||||
s.execute("paint.setFill", &json!({"ids": [dst.0], "color": "#00ff00"})).unwrap();
|
||||
s.execute("paint.setFill", &json!({"ids": [dst.0], "swatch": "Dots"})).unwrap();
|
||||
s.execute("pattern.transform", &json!({"ids": [dst.0], "matrix": [3, 0, 0, 3, 0, 0]})).unwrap();
|
||||
s.execute("paint.setFill", &json!({"ids": [dst.0], "swatch": "Picked"})).unwrap();
|
||||
let Paint::Pattern { xf, .. } = node(&s, dst).appearance.fill_paint() else { panic!("a pattern fill") };
|
||||
assert_eq!(xf.as_coeffs(), [3.0, 0.0, 0.0, 3.0, 0.0, 0.0], "the art keeps its own placement");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_glow_on_a_pattern_fill_stays_inside_the_object_on_the_threaded_renderer() {
|
||||
// #2426: the glow's pixmap was drawn with the pattern's paint transform, so it landed
|
||||
|
||||
+2
-1
@@ -597,7 +597,8 @@ global or spot swatch follow its colour and name, as one undo step, in the art,
|
||||
designs, pattern tiles, graphic styles and the art of brushes); `swatch.delete {names, unlink?}` deletes
|
||||
swatches and colour groups in one undo step (art using a deleted global swatch keeps its colour, unlinked).
|
||||
`swatch.new {color?, mode?, spot?, global?, group?}` saves a colour (into a colour group with `group`), gradient
|
||||
or pattern; `swatch.newGroup {fromArtwork: true, toGlobal?, includeTints?}` makes a colour group of the selected
|
||||
or pattern (a gradient swatch holds no link to the swatch its gradient came from, and a pattern swatch holds the
|
||||
pattern untransformed); `swatch.newGroup {fromArtwork: true, toGlobal?, includeTints?}` makes a colour group of the selected
|
||||
art's colours (by default as global swatches the art links to).
|
||||
`swatch.move {names, to?, group?}` reorders swatches or moves them into or out of a colour group (as dragging
|
||||
them in the Swatches panel does); give colour group names instead to reorder the groups. Dropping a swatch on art
|
||||
|
||||
Reference in New Issue
Block a user