Blend: a blend without a spine shows the Appearance widget

A blend made with Create Spine set to Never now shows the Appearance
widget with the Selection and Blend tools, so its color Shift can be
dragged on the canvas as on a blend with a spine. The Easing widget
stays hidden, since easing needs a spine.

The widget sits 16 px above the halfway point, by length, of the
straight lines between the key centers that the steps are laid out
along, and a drag follows those lines' direction there. The lines are
not drawn. `blend_layout_line` exposes that line; `blend_spine` still
returns `None` for such a blend.
This commit is contained in:
dotCooCoo
2026-11-13 06:17:47 -08:00
parent e8c7421a3d
commit fc70b08f71
9 changed files with 150 additions and 18 deletions
+30
View File
@@ -1369,6 +1369,14 @@ pub fn blend_spine(keys: &[Arc<Node>], spec: &BlendSpec) -> Option<(PathData, Op
layout_spine(keys, spec)
}
/// The line the steps of a blend of `keys` are placed along: [`blend_spine`]'s path for a blend
/// with a spine, and the straight lines between the key centers, in key order, for a blend made
/// without one ([`BlendSpec::no_spine`]). For a blend without a spine the line places the steps
/// only; it is not a spine and is not drawn as one. `None` with fewer than two keys.
pub fn blend_layout_line(keys: &[Arc<Node>], spec: &BlendSpec) -> Option<PathData> {
layout_spine(keys, spec).map(|(path, _)| path)
}
/// The spine `spec` stores, unless the blend was made without one.
fn stored_spine(spec: &BlendSpec) -> Option<&PathData> {
spec.spine.as_ref().filter(|_| !spec.no_spine)
@@ -1840,6 +1848,28 @@ mod tests {
assert_eq!(moved.subpaths[0].anchors[2].p, Point::new(305.0, 5.0));
}
#[test]
fn a_blend_without_a_spine_has_a_layout_line_through_its_key_centers() {
let at =
|x: f64, y: f64| Arc::new(Node::path(NodeId(1), shapes::rectangle(Rect::new(x - 5.0, y - 5.0, x + 5.0, y + 5.0)), Appearance::default()));
let line_of = |path: &PathData| path.subpaths.iter().flat_map(|s| s.anchors.iter().map(|a| a.p)).collect::<Vec<_>>();
// A stored spine is ignored once the blend is made without one.
let stored = PathData::single(SubPath::polyline(&[Point::new(0.0, 50.0), Point::new(300.0, 50.0)], false));
let spec = BlendSpec { no_spine: true, spine: Some(stored), ..Default::default() };
for keys in [vec![at(10.0, 20.0), at(110.0, 20.0)], vec![at(10.0, 20.0), at(110.0, 20.0), at(110.0, 220.0), at(-40.0, 0.0)]] {
assert!(blend_spine(&keys, &spec).is_none(), "still no spine");
let line = blend_layout_line(&keys, &spec).unwrap();
assert_eq!(line.subpaths.len(), 1);
assert!(!line.subpaths[0].closed);
let centers: Vec<Point> = keys.iter().map(|k| center_of(k)).collect();
assert_eq!(line_of(&line), centers, "in key order");
}
// With a spine, the layout line is the spine.
let keys = vec![key(0.0), key(100.0), key(300.0)];
assert_eq!(blend_layout_line(&keys, &BlendSpec::default()), blend_spine(&keys, &BlendSpec::default()).map(|(p, _)| p));
assert!(blend_layout_line(&keys[..1], &spec).is_none(), "one key");
}
#[test]
fn steps_follow_a_bent_spine() {
let keys = vec![key(0.0), key(100.0)];
+1 -1
View File
@@ -39,7 +39,7 @@ fn fifty() -> f64 {
// =====================================================================================
pub use crate::blend::{
Spine, blend_expand, blend_spine, blend_step_count, lerp_appearance, lerp_color, lerp_node, lerp_paint, lerp_path, pin_spine,
Spine, blend_expand, blend_layout_line, blend_spine, blend_step_count, lerp_appearance, lerp_color, lerp_node, lerp_paint, lerp_path, pin_spine,
steps_knockout_shows,
};