Select Same: Fill Color from a Live Paint face stays in its Live Paint group

Select > Same > Fill Color and Fill & Stroke from a face of a Live Paint
group now select the matching faces of that group only, and from any
other object they select no Live Paint face. Before, they matched faces
of every Live Paint group and paths outside them alike.
This commit is contained in:
dotCooCoo
2026-10-31 19:43:14 +07:00
committed by bflatastic
parent 9163c22396
commit 7860931a3b
2 changed files with 70 additions and 6 deletions
+28 -6
View File
@@ -5,6 +5,7 @@ use std::collections::BTreeSet;
use serde_json::{Value, json};
use vectorcraft_color::Paint;
use vectorcraft_doc::{Document, Node, NodeId, NodeKind};
use vectorcraft_tools::builder::is_live_paint;
use super::wand::{fill_of, stroke_of};
use super::{
@@ -106,11 +107,11 @@ pub fn specs() -> Vec<CommandSpec> {
"Fill Color",
["Select", "Same"],
None,
"{} the objects filled with the first selected object's fill colour (any tint of its global or spot swatch, the same tint with prefs selectSameTintPercent); a member path of a compound path selected first compares as its compound path, except in isolation mode inside that compound path",
"{} the objects filled with the first selected object's fill colour (any tint of its global or spot swatch, the same tint with prefs selectSameTintPercent); a member path of a compound path selected first compares as its compound path, except in isolation mode inside that compound path; from a Live Paint face, the faces of its Live Paint group only, and from any other object, no Live Paint face",
has_selection,
|s, _| {
let tint = s.prefs.select_same_tint_percent;
same(s, "select.same.fillColor", |a, b| same_paint(&fill_of(a), &fill_of(b), tint))
same_fill(s, "select.same.fillColor", |a, b| same_paint(&fill_of(a), &fill_of(b), tint))
}
),
cmd!(
@@ -135,11 +136,11 @@ pub fn specs() -> Vec<CommandSpec> {
"Fill & Stroke",
["Select", "Same"],
None,
"{} the objects with the first selected object's fill and stroke colours (tints and compound paths as select.same.fillColor)",
"{} the objects with the first selected object's fill and stroke colours (tints, compound paths and Live Paint faces as select.same.fillColor)",
has_selection,
|s, _| {
let tint = s.prefs.select_same_tint_percent;
same(s, "select.same.fillAndStroke", |a, b| {
same_fill(s, "select.same.fillAndStroke", |a, b| {
same_paint(&fill_of(a), &fill_of(b), tint) && same_paint(&stroke_of(a).0, &stroke_of(b).0, tint)
})
}
@@ -684,9 +685,30 @@ fn same_reference<'a>(st: &'a DocState, cmd: &str) -> Result<(&'a Node, bool)> {
/// [`paint_reported`] accepts. A compound path matches as a whole. In isolation mode inside a
/// compound path, its member paths are matched instead.
fn same(s: &mut Session, cmd: &str, eq: impl Fn(&Node, &Node) -> bool) -> Result<Value> {
let (refn, members) = same_reference(s.doc()?, cmd)?;
same_in(s, cmd, false, eq)
}
/// Select > Same > Fill Color and Fill & Stroke: [`same`], kept to one side of a Live Paint group.
/// From a face or edge of a Live Paint group, only the faces and edges of that group match; from
/// any other object, no face or edge of a Live Paint group does
/// (<https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/select-objects-with-same-fill-and-stroke.html>).
fn same_fill(s: &mut Session, cmd: &str, eq: impl Fn(&Node, &Node) -> bool) -> Result<Value> {
same_in(s, cmd, true, eq)
}
fn same_in(s: &mut Session, cmd: &str, live_paint: bool, eq: impl Fn(&Node, &Node) -> bool) -> Result<Value> {
let st = s.doc()?;
let (refn, members) = same_reference(st, cmd)?;
let group = live_paint_group(&st.doc, refn.id);
let refn = refn.clone();
select_where(s, cmd, &json!({}), |d, n| (if members { !n.is_container() } else { paint_reported(d, n) }) && eq(n, &refn))
select_where(s, cmd, &json!({}), |d, n| {
(!live_paint || live_paint_group(d, n.id) == group) && (if members { !n.is_container() } else { paint_reported(d, n) }) && eq(n, &refn)
})
}
/// The Live Paint group `id` is a face or edge of.
fn live_paint_group(d: &Document, id: NodeId) -> Option<NodeId> {
d.parent_of(id).filter(|p| d.node(*p).is_some_and(is_live_paint))
}
/// Select the objects [`walk_selectable`] visits that `f` accepts (not looking inside accepted
+42
View File
@@ -1813,6 +1813,48 @@ fn select_same_matches_compound_paths_on_their_own_paint() {
assert_eq!(same(&mut s, outer, "select.same.shapeType"), vec![outer, inner]);
}
/// Select > Same > Fill Color and Fill & Stroke from a Live Paint face select the faces of that
/// Live Paint group with the same fill, and nothing outside it. From an object outside every Live
/// Paint group, they select no face.
#[test]
fn select_same_fill_keeps_live_paint_faces_to_their_group() {
let mut s = session();
let live = |s: &mut Session, y: f64| {
let a = rect(s, 0.0, y, 100.0, 100.0);
let b = rect(s, 50.0, y, 100.0, 100.0);
sel(s, &[a, b]);
id_of(&s.execute("livePaint.make", &json!({})).unwrap())
};
let paint = |s: &mut Session, g: NodeId, x: f64, y: f64, hex: &str| {
NodeId(s.execute("livePaint.fill", &json!({"group": g.0, "point": [x, y], "color": hex})).unwrap()["face"].as_u64().unwrap())
};
let g1 = live(&mut s, 0.0);
let g2 = live(&mut s, 300.0);
let [left, right] = [25.0, 125.0].map(|x| paint(&mut s, g1, x, 50.0, "#ff0000"));
let middle = paint(&mut s, g1, 75.0, 50.0, "#0000ff");
let other = paint(&mut s, g2, 25.0, 350.0, "#ff0000");
let [r1, r2] = [400.0, 450.0].map(|x| rect(&mut s, x, 0.0, 10.0, 10.0));
for r in [r1, r2] {
fill(&mut s, r, "#ff0000");
s.execute("paint.setStroke", &json!({"none": true, "ids": [r.0]})).unwrap();
}
let same = |s: &mut Session, from: NodeId, cmd: &str| {
sel(s, &[from]);
s.execute(cmd, &json!({})).unwrap();
let mut v = selected(s);
v.sort();
v
};
for cmd in ["select.same.fillColor", "select.same.fillAndStroke"] {
let mut faces = vec![left, right];
faces.sort();
assert_eq!(same(&mut s, left, cmd), faces, "{cmd} from a face");
assert_eq!(same(&mut s, middle, cmd), vec![middle], "{cmd} from the blue face");
assert_eq!(same(&mut s, other, cmd), vec![other], "{cmd} from the other group's face");
assert_eq!(same(&mut s, r1, cmd), vec![r1, r2], "{cmd} from a path outside");
}
}
/// Select by Color needs no object to start from: it matches fills and strokes as
/// `document.inspect` reports them, `#rrggbb`.
#[test]