From 6546136a4217e1b3ba67e28e7aaf03bc0f04db34 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 3 Nov 2026 08:49:18 -0500 Subject: [PATCH] PPTX timing: a sorted click group keeps its click on the effect that now plays first --- crates/pptx/src/read/timing.rs | 5 ++++- crates/pptx/tests/it/parent_delays.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/pptx/src/read/timing.rs b/crates/pptx/src/read/timing.rs index f3166d622..1b6aa8b1b 100644 --- a/crates/pptx/src/read/timing.rs +++ b/crates/pptx/src/read/timing.rs @@ -151,6 +151,8 @@ fn place_by_parent_delays(out: &mut [(Animation, u32, String, bool)], at: &[(Opt if flat == abs { continue; } + // The group's click stays on whichever effect now plays first. + let had_click = out[idx[0]].0.start == AnimStart::OnClick; let mut order: Vec = (0..idx.len()).collect(); order.sort_by_key(|&k| abs[k]); let mut sorted: Vec<(Animation, u32, String, bool)> = order.iter().map(|&k| out[idx[k]].clone()).collect(); @@ -158,7 +160,8 @@ fn place_by_parent_delays(out: &mut [(Animation, u32, String, bool)], at: &[(Opt for (n, (&k, item)) in order.iter().zip(sorted.iter_mut()).enumerate() { let a = &mut item.0; let t = abs[k]; - if n == 0 && a.start == AnimStart::OnClick { + if n == 0 && had_click { + a.start = AnimStart::OnClick; a.delay_ms = t; } else if a.start == AnimStart::AfterPrevious && t >= end { begin = end; diff --git a/crates/pptx/tests/it/parent_delays.rs b/crates/pptx/tests/it/parent_delays.rs index 9dcaa8518..8d9f25c61 100644 --- a/crates/pptx/tests/it/parent_delays.rs +++ b/crates/pptx/tests/it/parent_delays.rs @@ -126,3 +126,20 @@ fn a_timeline_already_in_order_is_read_unchanged() { let starts: Vec<_> = fx.iter().map(|(n, st, d)| (n.as_str(), *st, *d)).collect(); assert_eq!(starts, [("Three", AnimStart::OnClick, 0), ("Visual", AnimStart::WithPrevious, 500), ("Two", AnimStart::AfterPrevious, 1000)]); } + +#[test] +fn a_delayed_click_effect_sorted_after_a_with_effect_keeps_the_click_on_the_group() { + // The click effect waits 500 ms; a with-effect in the same click plays at once, and a later + // effect is placed only by its parent's delay. Sorted, the with-effect plays first and takes + // the click, so the group still waits for one. + let fx = import_with_timing(|s| { + [ + group(10, 0, &[eff(11, "exit", "clickEffect", 500, s[0]), eff(12, "entr", "withEffect", 0, s[3])].concat()), + group(20, 1000, &eff(21, "exit", "afterEffect", 0, s[1])), + ] + .concat() + }); + let starts: Vec<_> = fx.iter().map(|(n, st, _)| (n.as_str(), *st)).collect(); + assert_eq!(starts[0], ("Visual", AnimStart::OnClick), "{fx:?}"); + assert_eq!(starts.iter().filter(|(_, st)| *st == AnimStart::OnClick).count(), 1, "{fx:?}"); +}