mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 10:47:11 -06:00
Live Paint: a plain group named "Live Paint..." is not a Live Paint group
A group counted as a Live Paint group when its name started with "Live Paint". A plain group named, for example, "Live Painting" was then read as one. With it and another shape selected, the Shape Builder found no regions in it, and a merge on the other shape deleted the group and its paths. Make Live Paint took the group as a Live Paint group to merge into, and its paths did not become sources. A Live Paint group now also needs its sources group, the group named "Live Paint Sources" that Make Live Paint puts in it. A plain group with such a name is traversed like any other group.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
//! Shape Builder (`shapeBuilder.*`), Live Paint (`livePaint.*`) and Image Trace (`imageTrace.*`).
|
||||
//!
|
||||
//! **Live Paint groups** need no special node kind: a Live Paint group is a plain group whose name
|
||||
//! starts with "Live Paint". Its first child is a hidden group named "Live Paint Sources" holding
|
||||
//! the original paths (the planar map's inputs, used by Release, Merge and re-computation); then
|
||||
//! come the faces (paths named "Face", fill only) and the edges (open paths named "Edge", stroke
|
||||
//! only). See `vectorcraft_tools::builder` for the shared helpers.
|
||||
//! starts with "Live Paint" and that holds a group named "Live Paint Sources". That group is its
|
||||
//! first child, hidden, and holds the original paths (the planar map's inputs, used by Release,
|
||||
//! Merge and re-computation); then come the faces (paths named "Face", fill only) and the edges
|
||||
//! (open paths named "Edge", stroke only). See `vectorcraft_tools::builder` for the shared helpers.
|
||||
//!
|
||||
//! **Image Trace** makes a group named "Image Trace" holding the source image (hidden, bottom)
|
||||
//! and the traced filled paths above it, mapped with the image's transform. Expand drops the
|
||||
|
||||
@@ -2199,6 +2199,42 @@ fn shape_builder_keeps_the_edges_of_an_unfilled_live_paint_group() {
|
||||
assert!((stroked_length(&s, &out) - 700.0).abs() < 1.0, "{}", stroked_length(&s, &out));
|
||||
}
|
||||
|
||||
/// A plain group whose name starts with "Live Paint" is not a Live Paint group: the Shape Builder
|
||||
/// takes its paths, and a merge elsewhere keeps them.
|
||||
#[test]
|
||||
fn shape_builder_takes_the_paths_of_a_plain_group_named_live_paint() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
set_fill(&mut s, a, Color::rgb(1.0, 0.0, 0.0));
|
||||
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
let g = NodeId(s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap());
|
||||
s.edit("t", |d, _| {
|
||||
d.node_mut(g).unwrap().name = Some("Live Painting".into());
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let c = rect(&mut s, 160.0, 20.0, 100.0, 20.0);
|
||||
set_fill(&mut s, c, Color::rgb(0.0, 1.0, 0.0));
|
||||
s.execute("select.set", &json!({"ids": [g.0, c.0]})).unwrap();
|
||||
let map = s.execute("shapeBuilder.regions", &json!({})).unwrap();
|
||||
assert_eq!(map["regions"].as_array().unwrap().len(), 2, "{map}");
|
||||
let r = s.execute("shapeBuilder.merge", &json!({"points": [[200, 30]]})).unwrap();
|
||||
let out = ids(&r);
|
||||
assert!(out.iter().any(|i| (area(&s, *i) - 10000.0).abs() < 1.0), "the group's rectangle is kept: {r}");
|
||||
// A stroke of the group's rectangle splits as a plain path's does.
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [g.0]})).unwrap();
|
||||
let r = s.execute("shapeBuilder.merge", &json!({"points": [[-1, 50]], "splitStroke": true})).unwrap();
|
||||
assert_eq!(node(&s, g).children().unwrap().len(), 2, "{r}");
|
||||
// Make Live Paint takes the group as a source rather than as a Live Paint group to merge into.
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [g.0]})).unwrap();
|
||||
let r = s.execute("livePaint.make", &json!({})).unwrap();
|
||||
assert_eq!(r["faces"], 1, "{r}");
|
||||
let made = node(&s, NodeId(r["id"].as_u64().unwrap()));
|
||||
assert!(vectorcraft_tools::builder::sources(&made).unwrap().children().unwrap().iter().any(|c| c.id == g));
|
||||
}
|
||||
|
||||
// ---------- Image Trace ----------
|
||||
|
||||
/// A 100×100 image with a black disc (r = 30 px), placed at (50, 50) scaled ×2.
|
||||
|
||||
@@ -23,7 +23,7 @@ use vectorcraft_pathops as po;
|
||||
|
||||
use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey, json_ids};
|
||||
|
||||
/// Names starting with this mark a Live Paint group.
|
||||
/// Names starting with this mark a Live Paint group, together with its sources group ([`SOURCES_NAME`]).
|
||||
pub const LIVE_PAINT_PREFIX: &str = "Live Paint";
|
||||
/// Default name of a new Live Paint group.
|
||||
pub const LIVE_PAINT_NAME: &str = "Live Paint Group";
|
||||
@@ -36,9 +36,12 @@ pub const EDGE_NAME: &str = "Edge";
|
||||
pub const HATCH: [u8; 3] = [0x80, 0x80, 0x80];
|
||||
pub const HIGHLIGHT_RED: [u8; 3] = [0xff, 0x2a, 0x2a];
|
||||
|
||||
/// Is `n` a Live Paint group?
|
||||
/// Is `n` a Live Paint group: an unclipped group whose name starts with [`LIVE_PAINT_PREFIX`] and
|
||||
/// that holds a group named [`SOURCES_NAME`]? A plain group with such a name is not one.
|
||||
pub fn is_live_paint(n: &Node) -> bool {
|
||||
matches!(n.kind, NodeKind::Group { clip: false, .. }) && n.name.as_deref().is_some_and(|s| s.starts_with(LIVE_PAINT_PREFIX))
|
||||
matches!(n.kind, NodeKind::Group { clip: false, .. })
|
||||
&& n.name.as_deref().is_some_and(|s| s.starts_with(LIVE_PAINT_PREFIX))
|
||||
&& sources(n).is_some_and(|s| matches!(s.kind, NodeKind::Group { .. }))
|
||||
}
|
||||
|
||||
/// Whether Live Paint can take `n` as it is: a Live Paint group, a path or compound path, or a
|
||||
@@ -2099,6 +2102,14 @@ mod tests {
|
||||
assert!(h.elements().len() > 10);
|
||||
}
|
||||
|
||||
/// An empty hidden sources group for a Live Paint group made in a test.
|
||||
fn empty_sources(d: &mut Document) -> Arc<Node> {
|
||||
let mut g = Node::group(d.alloc_id(), vec![]);
|
||||
g.name = Some(SOURCES_NAME.into());
|
||||
g.visible = false;
|
||||
Arc::new(g)
|
||||
}
|
||||
|
||||
fn live_group_doc() -> (Document, NodeId, NodeId, NodeId) {
|
||||
let mut d = Document::new(500.0, 500.0);
|
||||
let l = d.layers[0].id;
|
||||
@@ -2112,7 +2123,7 @@ mod tests {
|
||||
let mut edge = Node::path(e, shapes::line(Point::new(50.0, 0.0), Point::new(50.0, 50.0)), Appearance::default());
|
||||
edge.name = Some(EDGE_NAME.into());
|
||||
let gid = d.alloc_id();
|
||||
let mut g = Node::group(gid, vec![Arc::new(face1), Arc::new(face2), Arc::new(edge)]);
|
||||
let mut g = Node::group(gid, vec![empty_sources(&mut d), Arc::new(face1), Arc::new(face2), Arc::new(edge)]);
|
||||
g.name = Some(LIVE_PAINT_NAME.into());
|
||||
d.insert(Some(l), 0, g).unwrap();
|
||||
(d, gid, f2, e)
|
||||
@@ -2125,7 +2136,7 @@ mod tests {
|
||||
let mut face = Node::path(d.alloc_id(), shapes::rectangle(r), Appearance::default());
|
||||
face.name = Some(FACE_NAME.into());
|
||||
let (a, b, c, e) = (Point::new(r.x0, r.y0), Point::new(r.x1, r.y0), Point::new(r.x1, r.y1), Point::new(r.x0, r.y1));
|
||||
let mut kids = vec![Arc::new(face)];
|
||||
let mut kids = vec![empty_sources(d), Arc::new(face)];
|
||||
let mut ids = [NodeId(0); 4];
|
||||
for (k, (from, to)) in [(e, a), (a, b), (b, c), (c, e)].into_iter().enumerate() {
|
||||
ids[k] = d.alloc_id();
|
||||
|
||||
Reference in New Issue
Block a user