Play From: skip triggered effects, reject an invalid from

- The pane picks the first effect of the selected shape that is on the click sequence, so a
  triggered effect before it no longer turns the button back into Play All; a clicked triggered
  row falls back the same way.
- animation.preview rejects a from that is not an effect index instead of playing all.
- target-app-parity: the Animation Pane timeline is no longer a shortfall.
This commit is contained in:
Marco Arnulfo
2026-10-23 10:22:51 +02:00
parent 19ebd9c2dc
commit da9bbac5a0
4 changed files with 35 additions and 6 deletions
+27
View File
@@ -162,6 +162,33 @@ fn moving_an_effect_forgets_the_clicked_row() {
assert!(at < 0.1, "back to the shape's first effect, not the spin now in that row: {at}");
}
#[test]
fn play_from_skips_a_triggered_effect() {
let mut h = harness();
// The zoomed shape's first effect becomes triggered by the faded shape; its second effect,
// after the 1 s fade, is still on the click sequence.
let first = h.state().session.active().unwrap().current_slide().unwrap().animations[0].shape.0;
h.state_mut().session.execute("animation.timing", &json!({"index": 1, "trigger": first})).unwrap();
h.state_mut().session.execute("animation.add", &json!({"effect": "pulse", "start": "afterPrevious", "duration": 1000})).unwrap();
h.run_steps(2);
let at = play(&mut h, "▶ Play From");
assert!((1.0..1.1).contains(&at), "from the shape's first effect on the click sequence: {at}");
// A clicked triggered row falls back the same way.
click_row(&mut h, 2, egui::PointerButton::Primary);
let at = play(&mut h, "▶ Play From");
assert!((1.0..1.1).contains(&at), "{at}");
}
#[test]
fn preview_rejects_a_from_that_is_not_an_effect_index() {
let mut h = harness();
for from in [json!("1"), json!(-1), json!(1.5)] {
assert!(h.state_mut().run("animation.preview", json!({"from": from})).is_err(), "{from}");
assert!(h.state().anim_preview_report().is_none(), "{from}");
}
assert_eq!(h.state_mut().run("animation.preview", json!({"from": 1})).unwrap()["from"], 1.0);
}
#[test]
#[ignore = "writes a screenshot for review"]
fn screenshot() {
+4 -1
View File
@@ -971,7 +971,10 @@ impl SlideApp {
self.anim_preview = None;
return Ok(json!({"playing": false}));
}
let from = p.get("from").and_then(Value::as_u64).map(|v| usize::try_from(v).unwrap_or(usize::MAX));
let from = match p.get("from") {
None | Some(Value::Null) => None,
Some(v) => Some(v.as_u64().map(|v| usize::try_from(v).unwrap_or(usize::MAX)).ok_or("`from` must be an effect index")?),
};
return self.start_anim_preview(from);
}
"app.home" => {
+3 -4
View File
@@ -1022,10 +1022,9 @@ fn animation(app: &mut SlideApp, ui: &mut Ui) {
// index would point at another effect.
let pick_id = egui::Id::new(("anim_pane_pick", st.uid, st.selection.slide));
let pick = ui.data(|m| m.get_temp::<usize>(pick_id));
let from = pick
.filter(|&i| anims.get(i).is_some_and(|a| selected.contains(&a.shape.0)))
.or_else(|| anims.iter().position(|a| selected.contains(&a.shape.0)))
.filter(|&i| tl.start_of(i).is_some());
// A triggered effect is not on the click sequence, so it can't be played from.
let eligible = |i: usize| anims.get(i).is_some_and(|a| selected.contains(&a.shape.0)) && tl.start_of(i).is_some();
let from = pick.filter(|&i| eligible(i)).or_else(|| (0..anims.len()).find(|&i| eligible(i)));
ui.horizontal(|ui| {
let playing = app.anim_preview_active();
let label = match (playing, from) {
+1 -1
View File
@@ -194,7 +194,7 @@ Features row of the dimension table keeps ~62% until the next full re-measure).
| Pictures | 7 | 60% | ~45% | 12–20 | SVG, EMF+ records, clipboard paste on Windows/Linux (#53), remove background, artistic-effect and correction depth | [file-format-parity.md](file-format-parity.md) |
| Media | 4 | 67% | ~60% | 8–14 | WMA/WMV decode, record audio/screen, 3-D models and stock content, playback options lost on PPTX round trip (#74) | [file-format-parity.md](file-format-parity.md) |
| Transitions | 4 | 100% | ~80% | 4–8 | 3-D transitions approximated in 2-D | [animation-parity.md](animation-parity.md) |
| Animations | 6 | 85% | ~65% | 10–16 | 64 of PowerPoint's ~190 effects, Animation Pane timeline, rewind lost on save (#77) | [animation-parity.md](animation-parity.md) |
| Animations | 6 | 85% | ~65% | 10–16 | 64 of PowerPoint's ~190 effects, rewind lost on save (#77) | [animation-parity.md](animation-parity.md) |
| Slide show and presenter view | 8 | 68% | ~60% | 10–16 | Second-display placement, laser from presenter view, recording, subtitles, ink depth | [slideshow-parity.md](slideshow-parity.md) |
| Review | 3 | 70% | ~45% | 8–14 | Compare depth, version history, real spelling dictionaries | [file-format-parity.md](file-format-parity.md) |
| Draw (ink) | 2 | 50% | ~30% | 5–9 | Pen tilt, ink to math, lasso, ink to shape/text depth | [hardware-parity.md](hardware-parity.md) |