Animations: nine PowerPoint exit effects import as themselves (#147)

Exit effects DeckCraft had no entry for (Peek Out, Strips, Blinds, Box,
Checkerboard, Diamond, Sink Down, Spinner, Contract) imported as a plain
Fade and lost their direction, so a Peek Out or Sink Down didn't move.
Each mirrors an entrance DeckCraft already plays, so it is played as that
entrance in reverse (Sink Down as Rise Up, leaving downward); the import
keeps PowerPoint's direction and saving writes a matching filter or
motion. Float Out now also writes its motion.

The preset ids and directions come from PowerPoint: every effect, as
entrance and exit, in every direction it accepts, saved and read back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
justinjohn0306
2026-10-12 17:59:59 +05:30
co-authored by Claude Opus 5.5
parent 0ec0cc5e81
commit bd61308cf9
6 changed files with 69 additions and 8 deletions
+9
View File
@@ -44,6 +44,15 @@ pub(crate) fn exit_base(effect: &str) -> &str {
"swivelOut" => "swivel",
"bounceOut" => "bounce",
"dissolveOut" => "dissolve",
"blindsOut" => "blinds",
"boxOut" => "box",
"checkerboardOut" => "checkerboard",
"diamondOut" => "diamond",
"peekOut" => "peek",
"stripsOut" => "strips",
"sinkDown" => "rise",
"spinnerOut" => "spinner",
"contract" => "expand",
other => other,
}
}
+12
View File
@@ -789,3 +789,15 @@ fn show_follows_links() {
assert_eq!(s.follow(&p, &Action::LastViewed, None), LinkJump::None);
assert_eq!(s.slide, 3);
}
/// #147: exits PowerPoint plays downward leave downward here too.
#[test]
fn sink_down_and_peek_out_to_bottom_move_down() {
for (effect, option) in [("sinkDown", ""), ("peekOut", "b"), ("floatOut", "d")] {
let mut a = anim(1, AnimClass::Exit, effect, AnimStart::OnClick);
a.option = option.into();
let tl = Timeline::new(&slide_with(vec![a]), W, H);
let s = tl.state(ShapeId(1), None, 0, 0.5);
assert!(s.offset_y > 0.0, "{effect} {option}: {s:?}");
}
}
+2 -1
View File
@@ -471,7 +471,8 @@ fn build_spec(a: &Animation, xf: Xfrm, sw: f64, sh: f64, resolve: &dyn Fn(&deckc
EffectSpec {
class: a.class,
effect,
option: a.option.clone(),
// Sink Down is Rise Up played backwards, leaving downward.
option: if a.effect == "sinkDown" { "d".into() } else { a.option.clone() },
accel: a.smooth_start,
decel: a.smooth_end,
bounce: a.bounce_end,
+9
View File
@@ -270,6 +270,15 @@ pub static ANIMATIONS: &[(&str, &str, AnimClass, u32, u32, &[&str])] = &[
("swivelOut", "Swivel", AnimClass::Exit, 45, 2000, &["horz", "vert"]),
("bounceOut", "Bounce", AnimClass::Exit, 26, 2000, &[]),
("dissolveOut", "Dissolve Out", AnimClass::Exit, 9, 500, &[]),
("blindsOut", "Blinds", AnimClass::Exit, 3, 500, &["horz", "vert"]),
("boxOut", "Box", AnimClass::Exit, 4, 500, &["in", "out"]),
("checkerboardOut", "Checkerboard", AnimClass::Exit, 5, 500, &["across", "down"]),
("diamondOut", "Diamond", AnimClass::Exit, 8, 500, &["in", "out"]),
("peekOut", "Peek Out", AnimClass::Exit, 12, 500, &["b", "l", "t", "r"]),
("stripsOut", "Strips", AnimClass::Exit, 18, 500, &["lu", "ru", "ld", "rd"]),
("sinkDown", "Sink Down", AnimClass::Exit, 37, 1000, &[]),
("spinnerOut", "Spinner", AnimClass::Exit, 49, 1000, &[]),
("contract", "Contract", AnimClass::Exit, 55, 500, &[]),
("lines", "Lines", AnimClass::Path, 64, 2000, &["down", "up", "right", "left"]),
("arcs", "Arcs", AnimClass::Path, 37, 2000, &["down", "up", "right", "left"]),
("turns", "Turns", AnimClass::Path, 43, 2000, &["down", "up", "right", "left"]),
+25
View File
@@ -256,6 +256,12 @@ const SUBTYPES: &[(&str, &[(&str, u32)])] = &[
("blinds", HV),
("swivel", HV),
("swivelOut", HV),
("blindsOut", HV),
("boxOut", INOUT),
("checkerboardOut", &[("across", 10), ("down", 5)]),
("diamondOut", INOUT),
("peekOut", DIR4),
("stripsOut", &[("lu", 9), ("ru", 3), ("ld", 12), ("rd", 6)]),
("checkerboard", &[("across", 10), ("down", 5)]),
("wheel", WHEEL),
("wheelOut", WHEEL),
@@ -329,6 +335,25 @@ mod tests {
use super::*;
use crate::xml::parse;
/// #147: these exits, with their directions, fell back to a plain fade (PowerPoint's ids).
#[test]
fn exit_presets_import_as_their_own_effects() {
for (preset, sub, effect, option) in [
(3, 10, "blindsOut", "horz"),
(4, 32, "boxOut", "out"),
(5, 5, "checkerboardOut", "down"),
(8, 16, "diamondOut", "in"),
(12, 4, "peekOut", "b"),
(18, 6, "stripsOut", "rd"),
(37, 0, "sinkDown", ""),
(49, 0, "spinnerOut", ""),
(55, 0, "contract", ""),
] {
let e = effect_for_preset(AnimClass::Exit, preset).expect("known exit");
assert_eq!((e, option_from_subtype(e, sub).as_str()), (effect, option), "preset {preset}");
}
}
#[test]
fn every_transition_kind_round_trips() {
for (kind, _, _, _, opts) in deckcraft_model::anim::TRANSITIONS {
+12 -7
View File
@@ -208,14 +208,14 @@ fn filter_for(effect: &str, option: &str) -> &'static str {
"randombar(horizontal)"
}
}
"blinds" => {
"blinds" | "blindsOut" => {
if option == "vert" {
"blinds(vertical)"
} else {
"blinds(horizontal)"
}
}
"box" => {
"box" | "boxOut" => {
if option == "out" {
"box(out)"
} else {
@@ -229,7 +229,7 @@ fn filter_for(effect: &str, option: &str) -> &'static str {
"circle(in)"
}
}
"diamond" => {
"diamond" | "diamondOut" => {
if option == "out" {
"diamond(out)"
} else {
@@ -246,7 +246,7 @@ fn filter_for(effect: &str, option: &str) -> &'static str {
"plusOut" => "plus(out)",
_ => "circle(in)",
},
"checkerboard" => {
"checkerboard" | "checkerboardOut" => {
if option == "down" {
"checkerboard(down)"
} else {
@@ -261,13 +261,13 @@ fn filter_for(effect: &str, option: &str) -> &'static str {
"8" => "wheel(8)",
_ => "wheel(1)",
},
"strips" => match option {
"strips" | "stripsOut" => match option {
"ru" => "strips(upRight)",
"ld" => "strips(downLeft)",
"rd" => "strips(downRight)",
_ => "strips(upLeft)",
},
"peek" => match option {
"peek" | "peekOut" => match option {
"t" => "slide(fromTop)",
"l" => "slide(fromLeft)",
"r" => "slide(fromRight)",
@@ -328,7 +328,12 @@ fn behaviors(w: &mut W, x: &mut Exp, ids: &mut Ids, e: &Eff) {
anim_prop(w, ids, e, dur, "ppt_x", "#ppt_x", fx);
anim_prop(w, ids, e, dur, "ppt_y", "#ppt_y", fy);
}
"zoomOut" | "shrinkTurn" => {
"floatOut" | "sinkDown" => {
let to = if a.effect == "sinkDown" || a.option == "d" { "#ppt_y+.1" } else { "#ppt_y-.1" };
anim_prop(w, ids, e, dur, "ppt_y", "#ppt_y", to);
anim_effect(w, ids, e, dur, "fade", true);
}
"zoomOut" | "shrinkTurn" | "spinnerOut" | "contract" => {
anim_prop(w, ids, e, dur, "ppt_w", "#ppt_w", "0");
anim_prop(w, ids, e, dur, "ppt_h", "#ppt_h", "0");
anim_effect(w, ids, e, dur, "fade", true);