Merge PR #136 from alexanderalber: Morph: text boxes without a partner fade in place instead of moving (#119)

This commit is contained in:
Brandon Thomas
2026-10-14 15:04:43 -04:00
3 changed files with 27 additions and 3 deletions
+5 -2
View File
@@ -12,7 +12,9 @@ fn text_of(s: &Shape) -> String {
/// Pairs of (shape on `a`, shape on `b`) that Morph animates from one to the other: first by
/// `!!name` (forced match), then by exact name, then by the same kind and text, then by the same
/// kind and preset geometry. Each shape is used once; top-level shapes only (groups move whole).
/// kind and preset geometry if neither carries text. A text box with neither a name nor its text on
/// the other slide stays unpaired and fades in place, as in PowerPoint (#119). Each shape is used
/// once; top-level shapes only (groups move whole).
pub fn morph_pairs(a: &Slide, b: &Slide) -> Vec<(ShapeId, ShapeId)> {
let mut pairs: Vec<(ShapeId, ShapeId)> = Vec::new();
let mut used_a = vec![false; a.shapes.len()];
@@ -31,7 +33,8 @@ pub fn morph_pairs(a: &Slide, b: &Slide) -> Vec<(ShapeId, ShapeId)> {
x.kind_name() == y.kind_name()
&& x.geom.preset_name().is_some()
&& x.geom.preset_name() == y.geom.preset_name()
&& text_of(x).trim().is_empty() == text_of(y).trim().is_empty()
&& text_of(x).trim().is_empty()
&& text_of(y).trim().is_empty()
},
];
for pass in passes {
+20
View File
@@ -529,6 +529,26 @@ fn morph_pairs_by_name_text_and_geometry() {
assert!(morph_pairs(&Slide::default(), &b).is_empty());
}
#[test]
fn morph_pairs_leaves_unrelated_text_boxes_alone() {
// A text box that leaves and an unrelated one that arrives fade in place (#119).
let p = deckcraft_model::defaults::new_presentation(None);
let mut a = Slide::default();
let mut b = Slide::default();
let tb = |id, name, x, text| Shape { xfrm: Some(Xfrm::new(x, 100.0, 200.0, 100.0)), text: Some(TextBody::from_text(text)), ..shape(id, name) };
a.shapes = vec![tb(1, "TextBox 1", 0.0, "Gone")];
b.shapes = vec![tb(2, "TextBox 2", 400.0, "New")];
assert!(morph_pairs(&a, &b).is_empty());
let f = morph_frame(&p, &a, &b, 0.5, &|_| false);
let xs: Vec<(&str, f64)> = f.shapes.iter().map(|(s, _)| (s.name.as_str(), s.xfrm.map_or(f64::NAN, |x| x.x))).collect();
assert_eq!(xs, [("TextBox 1", 0.0), ("TextBox 2", 400.0)]);
// Empty shapes of one kind and geometry still pair, and a named text box keeps its partner.
a.shapes.push(shape(3, "Rect A"));
b.shapes.push(shape(4, "Rect B"));
b.shapes[0].name = "TextBox 1".into();
assert_eq!(morph_pairs(&a, &b), [(ShapeId(1), ShapeId(2)), (ShapeId(3), ShapeId(4))]);
}
#[test]
fn morph_xfrm_interpolates() {
let a = Xfrm { rot: 350.0, ..Xfrm::new(0.0, 0.0, 100.0, 100.0) };
+2 -1
View File
@@ -42,7 +42,7 @@ perspective-correct subdivided meshes.
| Duration, advance on click / after time, Apply To All | yes | yes |
| Transition sounds | yes | yes: Transitions › Sound offers six built-in sounds (synthesised, no sound files shipped), Other Sound…, [Stop Previous Sound] and Loop Until Next Sound; they play in the show and round-trip |
| Preview in the editor | yes | yes |
| Morph: objects move, resize, recolour | yes | yes |
| Morph: objects move, resize, recolour; unmatched text boxes fade in place | yes | yes (#119) |
| Morph: Words and Characters | yes | yes (#5) |
| Morph: geometry (circle to star) | yes | yes (#27) |
| Morph: `!!` name forcing, pictures, 3-D models, ink | yes | names partly; no 3-D or ink |
@@ -99,4 +99,5 @@ In, Wipe, Zoom, Float In, Grow/Shrink, Pulse, Spin, motion lines): weighted, ~85
| 2026-10-14 | trivial | Repeat and bounce end round-trip through PPTX; rewind is still lost (#77) |
| 2026-10-13 | major | Transitions measured frame by frame against PowerPoint's video export and retuned (mean diff 35.7 → 15.0); PowerPoint's XML defaults and option meanings; new options |
| 2026-10-13 | minor | Triggers now play in the slide show (they were read and written but never started), effects of different triggers apply in the order they were clicked |
| 2026-10-11 | minor | Morph leaves unrelated text boxes unpaired: they fade in place (#119) |
| 2026-10-10 | major | Created: 48/48 transitions, 64 of ~190 animation effects, pane and option depth, estimates |