mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 07:17:08 +00:00
Tests: hidden layer children and edited freeform gradients under the Warp effects
A Warp on a layer whose hidden circle keeps its shape past the budget moves the layer's placed gradient from the layer's bounds to the warped square's bounds, as the art and inside the art. Edited freeform points on a path, on a group inside the art and on the art itself move from the bounds before the warp to the bounds after it.
This commit is contained in:
@@ -886,6 +886,108 @@ fn a_hidden_path_past_the_budget_leaves_a_layers_gradients_free_to_move() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Under a Warp effect a hidden path in a layer that keeps its shape past the budget does not hold
|
||||
/// the layer's gradient in place either: it moves and scales from the layer's bounds to the warped
|
||||
/// square's bounds, as the art and inside the art.
|
||||
#[test]
|
||||
fn a_hidden_path_past_the_budget_leaves_a_layers_gradients_free_to_follow_a_warp() {
|
||||
use std::sync::Arc;
|
||||
|
||||
use vectorcraft_color::{Gradient, GradientGeom, GradientKind, GradientPaint, GradientStop};
|
||||
use vectorcraft_doc::{AppearanceItem, FillLayer, LayerColor, NodeId, NodeKind, color::Paint};
|
||||
let stops = vec![GradientStop::new(0.0, vectorcraft_color::Color::BLACK), GradientStop::new(1.0, vectorcraft_color::Color::WHITE)];
|
||||
let geom = GradientGeom { start: (0.0, 50.0).into(), end: (100.0, 50.0).into(), aspect: 1.0, focal: None };
|
||||
let placed = Paint::Gradient(Box::new(GradientPaint { geom: Some(geom), ..GradientPaint::new(Gradient::new(GradientKind::Linear, stops)) }));
|
||||
let ends = |ap: &Appearance| match ap.fill_paint() {
|
||||
Paint::Gradient(g) => g.geom.map(|g| (g.start, g.end)),
|
||||
_ => None,
|
||||
};
|
||||
let oval = || shapes::ellipse(Rect::new(0.0, 0.0, 100.0, 100.0));
|
||||
// A layer with a visible square and a hidden circle, both 0..100, and a gradient fill of its own.
|
||||
let layer = || {
|
||||
let square = Arc::new(Node::path(NodeId(3), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), Appearance::default()));
|
||||
let mut circle = Node::path(NodeId(4), oval(), Appearance::default());
|
||||
circle.visible = false;
|
||||
let mut layer = Node::layer(NodeId(2), "Layer 1", LayerColor::Preset(0));
|
||||
if let NodeKind::Layer { children, .. } = &mut layer.kind {
|
||||
*children = vec![square, Arc::new(circle)];
|
||||
}
|
||||
layer.appearance.items.push(AppearanceItem::Fill(FillLayer::new(placed.clone())));
|
||||
layer
|
||||
};
|
||||
// The box map from the layer's bounds (the square's) to `b`: (0, 50) and (100, 50) go to the
|
||||
// middles of b's sides.
|
||||
let near = |got: Option<(Point, Point)>, b: Rect| {
|
||||
let mid = (b.y0 + b.y1) / 2.0;
|
||||
got.is_some_and(|(s, e)| s.distance(Point::new(b.x0, mid)) < 1e-6 && e.distance(Point::new(b.x1, mid)) < 1e-6)
|
||||
};
|
||||
for style in ["arc", "inflate", "flag"] {
|
||||
let warp = fx(&format!("warp.{style}"), json!({"bend": 40.0, "horizontal": 20.0}));
|
||||
// Room for the warped square beside the circle as it was, and not for the warped circle
|
||||
// beside the warped square: the paths take the budget in turn, so the circle keeps its shape.
|
||||
let mut probe = layer();
|
||||
crate::reshape::reshape_leaves_within(&mut probe, &[&warp], Budget::EFFECTS, None);
|
||||
let warped = |k: usize| util::line_estimate(probe.children().unwrap()[k].path_data().unwrap());
|
||||
let circle = util::line_estimate(&oval());
|
||||
assert!(warped(1) > circle, "warp.{style}: the warped circle is drawn with more lines");
|
||||
let budget = Budget { anchors: usize::MAX, lines: warped(0) + circle };
|
||||
let mut art = layer();
|
||||
let carry = crate::reshape::reshape_leaves_within(&mut art, &[&warp], budget, None);
|
||||
assert_eq!(art.children().unwrap()[1].path_data(), Some(&oval()), "{}: the hidden circle keeps its shape", warp.id);
|
||||
let square = art.children().unwrap()[0].geometric_bounds().unwrap();
|
||||
assert!(square != Rect::new(0.0, 0.0, 100.0, 100.0), "{}: the square takes the effect: {square:?}", warp.id);
|
||||
let mut own = Appearance::clone(&art.appearance);
|
||||
carry.apply(&mut own);
|
||||
assert!(near(ends(&own), square), "{}: the layer's gradient, as the art: {:?} for {square:?}", warp.id, ends(&own));
|
||||
let mut art = Node::group(NodeId(9), vec![Arc::new(layer())]);
|
||||
crate::reshape::reshape_leaves_within(&mut art, &[&warp], budget, None);
|
||||
let got = ends(&art.children().unwrap()[0].appearance);
|
||||
assert!(near(got, square), "{}: the layer's gradient, inside the art: {got:?} for {square:?}", warp.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// A freeform gradient whose points were edited moves its points with a Warp effect from the
|
||||
/// bounds before the warp to the bounds after it, on a path, on a group inside the art and on the
|
||||
/// art itself, as a placed gradient does.
|
||||
#[test]
|
||||
fn edited_freeform_points_follow_a_warp() {
|
||||
use std::sync::Arc;
|
||||
|
||||
use vectorcraft_color::{Color, Freeform, FreeformPoint, Gradient, GradientKind, GradientPaint};
|
||||
use vectorcraft_doc::{AppearanceItem, FillLayer, NodeId, color::Paint};
|
||||
let mut ramp = GradientPaint::new(Gradient { kind: GradientKind::Freeform, ..Gradient::default() });
|
||||
let pt = |x: f64, c| FreeformPoint::new(Point::new(x, 50.0), c);
|
||||
ramp.set_freeform(Freeform { points: vec![pt(0.0, Color::BLACK), pt(100.0, Color::WHITE)], ..Default::default() });
|
||||
assert!(ramp.geom.is_none() && !ramp.is_placed());
|
||||
let freeform = Paint::Gradient(Box::new(ramp));
|
||||
let points = |ap: &Appearance| match ap.fill_paint() {
|
||||
Paint::Gradient(g) => g.freeform.as_ref().map(|f| f.points.iter().map(|p| p.at).collect::<Vec<_>>()),
|
||||
_ => None,
|
||||
};
|
||||
let square = |ap: Appearance| Arc::new(Node::path(NodeId(3), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 100.0)), ap));
|
||||
// The box map from the square's bounds to `b`: (0, 50) and (100, 50) go to the middles of b's sides.
|
||||
let near = |got: Option<Vec<Point>>, b: Rect| {
|
||||
let mid = (b.y0 + b.y1) / 2.0;
|
||||
got.is_some_and(|p| p.len() == 2 && p[0].distance(Point::new(b.x0, mid)) < 1e-6 && p[1].distance(Point::new(b.x1, mid)) < 1e-6)
|
||||
};
|
||||
for style in ["arc", "inflate", "rise"] {
|
||||
let e = fx(&format!("warp.{style}"), json!({"bend": 40.0, "vertical": 20.0}));
|
||||
let mut inner = Node::group(NodeId(2), vec![square(Appearance::default())]);
|
||||
inner.appearance.items.push(AppearanceItem::Fill(FillLayer::new(freeform.clone())));
|
||||
let mut art = Node::group(NodeId(9), vec![square(Appearance::basic(freeform.clone(), Paint::None, 0.0)), Arc::new(inner)]);
|
||||
art.appearance.items.push(AppearanceItem::Fill(FillLayer::new(freeform.clone())));
|
||||
let carry = crate::reshape::reshape_leaves_within(&mut art, &[&e], Budget::EFFECTS, None);
|
||||
let b = art.geometric_bounds().unwrap();
|
||||
assert!(b != Rect::new(0.0, 0.0, 100.0, 100.0), "{}: the warp changes the bounds: {b:?}", e.id);
|
||||
let members = art.children().unwrap();
|
||||
assert!(near(points(&members[0].appearance), b), "{}: the path's points {:?} for {b:?}", e.id, points(&members[0].appearance));
|
||||
assert!(near(points(&members[1].appearance), b), "{}: the inner group's points {:?} for {b:?}", e.id, points(&members[1].appearance));
|
||||
let mut own = Appearance::clone(&art.appearance);
|
||||
carry.apply(&mut own);
|
||||
assert!(near(points(&own), b), "{}: the art's own points {:?} for {b:?}", e.id, points(&own));
|
||||
}
|
||||
}
|
||||
|
||||
/// A freeform gradient whose points were edited (its vector still fitted to the bounds) moves its
|
||||
/// points with a Transform effect and Free Distort, on a path, on a group inside the art and on the
|
||||
/// art itself, as a placed gradient does.
|
||||
|
||||
Reference in New Issue
Block a user