vectorcraft/crates/engine/src/tests_layerclip.rs

123 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Layer clipping masks (M3.75): the Layers panel button makes the top object of the current layer
//! (or of the selected group) clip the rest, and pressing it again releases the mask.
use serde_json::json;
use super::tests_clip::{assert_outputs_agree, hit, id_of, opaque, render, select, session};
use super::*;
fn rect(s: &mut Session, x: f64, y: f64, w: f64, h: f64) -> NodeId {
id_of(&s.execute("shape.rectangle", &json!({"x": x, "y": y, "width": w, "height": h})).unwrap())
}
fn toggle(s: &mut Session) -> bool {
s.execute("layer.clippingMask.toggle", &json!({})).unwrap()["clip"].as_bool().unwrap()
}
fn layer(s: &Session) -> vectorcraft_doc::Node {
let st = s.doc().unwrap();
st.doc.node(st.current_layer().unwrap()).unwrap().clone()
}
/// A full-page square under a 50 × 50 square at 25..75 (the clip to be), nothing selected.
fn art_and_clip(s: &mut Session) -> (NodeId, NodeId) {
let art = rect(s, 0.0, 0.0, 100.0, 100.0);
let clip = rect(s, 25.0, 25.0, 50.0, 50.0);
s.execute("select.none", &json!({})).unwrap();
(art, clip)
}
#[test]
fn the_top_object_clips_the_layer_and_pressing_again_releases_it() {
let mut s = session();
let (art, clip) = art_and_clip(&mut s);
assert!(toggle(&mut s));
let l = layer(&s);
assert!(l.clips());
// The clipping path loses its paint and goes to the bottom, so art added on top is clipped.
let c = &l.children().unwrap()[0];
assert_eq!(c.id, clip);
assert!(c.appearance.fill_paint().is_none() && matches!(c.kind, NodeKind::Path { clipping: true, .. }));
let r = render(&s);
assert!(opaque(&r, 50, 50) && !opaque(&r, 10, 10));
// Clicks only reach the art inside the clip.
assert_eq!(hit(&s, 50.0, 50.0), Some(art));
assert_eq!(hit(&s, 10.0, 10.0), None);
rect(&mut s, 0.0, 80.0, 100.0, 20.0);
assert!(!opaque(&render(&s), 50, 90), "new art is clipped");
// Again: released, the clipping path stays unpainted.
s.execute("select.none", &json!({})).unwrap();
assert!(!toggle(&mut s));
let l = layer(&s);
assert!(!l.clips());
let c = &l.children().unwrap()[0];
assert!(c.appearance.fill_paint().is_none() && matches!(c.kind, NodeKind::Path { clipping: false, .. }));
let r = render(&s);
assert!(opaque(&r, 10, 10) && opaque(&r, 50, 90));
}
#[test]
fn making_a_layer_clipping_mask_is_one_undo_step() {
let mut s = session();
let (_, clip) = art_and_clip(&mut s);
let before = layer(&s);
toggle(&mut s);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(layer(&s), before, "the clip path is back on top with its paint");
s.execute("edit.redo", &json!({})).unwrap();
assert!(layer(&s).clips());
assert_eq!(layer(&s).children().unwrap()[0].id, clip);
}
#[test]
fn layer_clipping_masks_save_and_export() {
let mut s = session();
art_and_clip(&mut s);
toggle(&mut s);
let doc = &s.doc().unwrap().doc;
let back = vectorcraft_format::load(&vectorcraft_format::save(doc, false)).unwrap();
assert!(back.layers[0].clips());
assert_eq!(back, **doc);
// An unclipped layer writes no `clip` key (older files stay as they were).
let plain = String::from_utf8(vectorcraft_format::save(&session().doc().unwrap().doc, false)).unwrap();
assert!(!plain.contains("\"clip\""), "{plain}");
// SVG and PDF clip the same way as the canvas.
assert_outputs_agree(&mut s, &[(50, 50), (10, 10), (30, 30), (80, 80), (74, 26)]);
}
#[test]
fn a_selected_group_becomes_a_clip_group() {
let mut s = session();
let (art, clip) = art_and_clip(&mut s);
select(&mut s, &[art, clip]);
let g = id_of(&s.execute("object.group", &json!({})).unwrap());
select(&mut s, &[g]);
assert!(toggle(&mut s));
let n = s.doc().unwrap().doc.node(g).unwrap().clone();
assert!(matches!(n.kind, NodeKind::Group { clip: true, .. }));
assert_eq!(n.children().unwrap()[0].id, clip);
assert!(!layer(&s).clips(), "the layer is left alone");
assert!(!opaque(&render(&s), 10, 10));
assert!(!toggle(&mut s));
assert!(!s.doc().unwrap().doc.node(g).unwrap().clips());
}
#[test]
fn the_top_object_must_be_able_to_clip() {
let mut s = session();
let a = rect(&mut s, 0.0, 0.0, 50.0, 50.0);
let b = rect(&mut s, 50.0, 50.0, 50.0, 50.0);
select(&mut s, &[a, b]);
s.execute("object.group", &json!({})).unwrap();
s.execute("select.none", &json!({})).unwrap();
let before = layer(&s);
assert!(s.execute("layer.clippingMask.toggle", &json!({})).is_err(), "a group on top");
assert_eq!(layer(&s), before);
// An explicit id picks the layer even with a group selected.
let l = before.id;
s.execute("layer.newSublayer", &json!({"parent": l.0})).unwrap();
let sub = layer(&s).children().unwrap().last().unwrap().id;
let err = s.execute("layer.clippingMask.toggle", &json!({"id": sub.0}));
assert!(err.is_err(), "an empty sublayer has nothing to clip by");
}