mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Outline Stroke, Expand: hidden and locked objects stay hidden and locked
The art that replaces an outlined or expanded path did not take the path's hidden, locked or Knockout Shape state. Outline Stroke on a hidden path, or Expand on a group with a hidden member, made the new art visible (a filled and stroked path became half visible: the outline shown, the fill copy hidden), and a locked member's art was unlocked. The replacement now takes the path's visibility, lock and Knockout Shape state, and the fill copies and clipping paths inside it are shown and unlocked, so showing or unlocking the new art shows or unlocks all of it. This covers Outline Stroke, and Expand's strokes, gradient fills and pattern fills.
This commit is contained in:
@@ -888,11 +888,13 @@ pub(crate) fn outline_strokes(d: &mut Document, brushes: &[vectorcraft_brush::Br
|
||||
}
|
||||
|
||||
/// A plain copy of path `l` (new ids, no transparency, name or mask) painted with `items`.
|
||||
/// The copy is shown and unlocked.
|
||||
pub(crate) fn styled_copy(d: &mut Document, l: &Node, items: Vec<AppearanceItem>) -> Node {
|
||||
let mut f = d.reid(l);
|
||||
f.appearance = Appearance { items, ..Default::default() }.into();
|
||||
f.opacity = 1.0;
|
||||
f.blend = BlendMode::Normal;
|
||||
(f.visible, f.locked, f.knockout_shape) = (true, false, false);
|
||||
f.name = None;
|
||||
f.extras_mut().mask = None;
|
||||
if let NodeKind::Path { live, .. } = &mut f.kind {
|
||||
@@ -903,7 +905,7 @@ pub(crate) fn styled_copy(d: &mut Document, l: &Node, items: Vec<AppearanceItem>
|
||||
|
||||
/// What stands for object `l` once its appearance became `parts` (paint order): the one part (in a
|
||||
/// group when it has effects of its own) or a group of them, with `l`'s transparency, name, opacity
|
||||
/// mask and effects. `None` without parts.
|
||||
/// mask, effects, and hidden, locked and knockout-shape states. `None` without parts.
|
||||
pub(crate) fn assemble(d: &mut Document, l: &Node, mut parts: Vec<Node>) -> Option<Node> {
|
||||
let root = match parts.len() {
|
||||
0 => return None,
|
||||
@@ -911,6 +913,7 @@ pub(crate) fn assemble(d: &mut Document, l: &Node, mut parts: Vec<Node>) -> Opti
|
||||
_ => Node::group(d.alloc_id(), parts.into_iter().map(Arc::new).collect()),
|
||||
};
|
||||
let mut root = with_transparency(d, root, l.opacity, l.blend);
|
||||
(root.visible, root.locked, root.knockout_shape) = (l.visible, l.locked, l.knockout_shape);
|
||||
root.name = l.name.clone();
|
||||
root.extras_mut().mask = l.extras().mask.clone();
|
||||
root.appearance.effects = l.appearance.effects.clone();
|
||||
|
||||
@@ -159,6 +159,35 @@ fn eight_strips_match_the_gradient_samples() {
|
||||
assert!(matches!(selected(&s).appearance.fill_paint(), Paint::Gradient(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expanding_a_group_keeps_its_hidden_and_locked_members_hidden_and_locked() {
|
||||
// A hidden stroked square with a gradient fill, a locked square with a pattern fill and a
|
||||
// plain square, grouped.
|
||||
let mut s = session();
|
||||
let pat = pattern_rect(&mut s, Rect::new(20.0, 100.0, 80.0, 160.0), false);
|
||||
let grad = gradient_rect(&mut s, Rect::new(20.0, 20.0, 80.0, 80.0), white_black(), true);
|
||||
let plain = NodeId(run(&mut s, "shape.rectangle", json!({"x": 100, "y": 20, "width": 40, "height": 40}))["id"].as_u64().unwrap());
|
||||
run(&mut s, "select.set", json!({"ids": [pat.0, grad.0, plain.0]}));
|
||||
run(&mut s, "object.group", json!({}));
|
||||
s.edit("t", |d, _| {
|
||||
d.node_mut(grad).unwrap().visible = false;
|
||||
d.node_mut(pat).unwrap().locked = true;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
run(&mut s, "object.expand", json!({}));
|
||||
let g = selected(&s);
|
||||
let ch = g.children().unwrap();
|
||||
assert_eq!(ch.len(), 3);
|
||||
assert!(!clip_parts(&ch[0]).1.is_empty() && ch[0].locked && ch[0].visible, "the pattern's tiles stay locked: {:?}", ch[0]);
|
||||
assert!(!ch[1].visible && !ch[1].locked, "the gradient square's art stays hidden: {:?}", ch[1]);
|
||||
assert!(ch[2].visible && !ch[2].locked);
|
||||
// Showing or unlocking the new art shows or unlocks all of it.
|
||||
for c in ch[..2].iter().flat_map(|c| c.children().unwrap().iter()) {
|
||||
c.walk(&mut |n| assert!(n.visible && !n.locked, "{n:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_radial_gradient_becomes_concentric_ellipses() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -122,6 +122,36 @@ fn every_stroke_is_outlined_in_paint_order() {
|
||||
assert_eq!(d.layers[0].children().unwrap()[0].appearance.items.len(), 3);
|
||||
}
|
||||
|
||||
/// The art that stands for an outlined path keeps the path's hidden, locked and knockout-shape
|
||||
/// states, so a hidden member of a selected group stays hidden.
|
||||
#[test]
|
||||
fn outlined_art_keeps_the_hidden_locked_and_knockout_shape_states() {
|
||||
let mut s = session();
|
||||
// A filled and stroked square (a fill copy and an outline) and a stroked line (one outline).
|
||||
let sq = NodeId(s.execute("shape.rectangle", &json!({"x": 0, "y": 100, "width": 50, "height": 50})).unwrap()["id"].as_u64().unwrap());
|
||||
s.execute("paint.setFill", &json!({"color": "#ff0000"})).unwrap();
|
||||
s.execute("paint.setStroke", &json!({"color": "#000000"})).unwrap();
|
||||
let ln = line(&mut s, json!({"weight": 4}));
|
||||
s.execute("select.set", &json!({"ids": [sq.0, ln.0]})).unwrap();
|
||||
s.execute("object.group", &json!({})).unwrap();
|
||||
s.edit("t", |d, _| {
|
||||
d.node_mut(sq).unwrap().visible = false;
|
||||
let l = d.node_mut(ln).unwrap();
|
||||
l.locked = true;
|
||||
l.knockout_shape = true;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let g = outline(&mut s);
|
||||
let ch = g.children().unwrap();
|
||||
assert_eq!(ch.len(), 2);
|
||||
let (sq, ln) = (&ch[0], &ch[1]);
|
||||
assert!(!sq.visible && !sq.locked, "the square's art stays hidden: {sq:?}");
|
||||
assert_eq!(sq.children().map(|c| c.len()), Some(2), "a fill copy and the outline");
|
||||
assert!(sq.children().unwrap().iter().all(|c| c.visible && !c.locked), "showing it shows all of it: {sq:?}");
|
||||
assert!(ln.visible && ln.locked && ln.knockout_shape, "the line's outline stays locked: {ln:?}");
|
||||
}
|
||||
|
||||
/// The live effect on a stroke item outlines that stroke (its own weight), not the top stroke.
|
||||
#[test]
|
||||
fn the_live_effect_outlines_the_stroke_it_sits_on() {
|
||||
|
||||
Reference in New Issue
Block a user