mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Animation Pane: Play From the selected effect
With an effect selected the pane's play button reads Play From and the canvas preview starts at that effect on the play-all clock, so the effects before it have already played. With nothing selected it is still Play All. animation.preview takes an optional from index.
This commit is contained in:
@@ -131,6 +131,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-22 | Animation Pane ▸ Play From: with an effect selected, the pane's play button starts the canvas preview at that effect, with the effects before it already played. With nothing selected it is still Play All |
|
||||
| 2026-10-18 | Native chart sidecar bytes move from JSON arrays into referenced ZIP entries, with shared media byte/item limits and bounded reads; older inline sidecars still load. Synthetic format regressions cover large workbooks and malformed members |
|
||||
| 2026-10-18 | Slide thumbnails and the Slide Sorter render only the slides in view: on a deck of more than 600 slides they used to evict their own thumbnails, so the pane flashed white and never finished (#969) |
|
||||
| 2026-10-17 | Opening a file caps absurd effect sizes (glow, soft edges, shadows, reflections, on shapes, text and table text) at what Format Shape allows: a glow of 10^18 pt in a .pptx made rendering ask for 34 GB and abort |
|
||||
@@ -241,6 +242,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-22 | trivial | Progress log: Animation Pane ▸ Play From. Scores unchanged |
|
||||
| 2026-10-18 | minor | Native chart payloads use referenced ZIP members and share media byte/item limits; old inline sidecars remain readable. Synthetic tests cover multi-megabyte layout, all shape owners, shared budgets, missing/corrupt members and invalid references. Scores and milestone scope unchanged |
|
||||
| 2026-10-15 | trivial | Progress log: chart package parts survive a native save. Scores and milestone names unchanged |
|
||||
| 2026-10-18 | trivial | Progress log: thumbnails of huge decks render only the slides in view (#969) |
|
||||
|
||||
@@ -344,6 +344,36 @@ fn empty_slide() {
|
||||
assert_eq!(tl.locate(5.0), (0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_of_seeks_the_play_all_clock_to_an_effect() {
|
||||
let mut late = anim(4, AnimClass::Entrance, "fade", AnimStart::WithPrevious);
|
||||
late.delay_ms = 250;
|
||||
let mut triggered = anim(4, AnimClass::Emphasis, "pulse", AnimStart::OnClick);
|
||||
triggered.trigger = Some(ShapeId(1));
|
||||
let tl = Timeline::new(
|
||||
&slide_with(vec![
|
||||
anim(1, AnimClass::Entrance, "fade", AnimStart::OnClick),
|
||||
anim(2, AnimClass::Exit, "fadeOut", AnimStart::OnClick),
|
||||
anim(3, AnimClass::Entrance, "fade", AnimStart::OnClick),
|
||||
late,
|
||||
triggered,
|
||||
]),
|
||||
W,
|
||||
H,
|
||||
);
|
||||
assert_eq!(tl.start_of(0), Some(0.0));
|
||||
assert_eq!(tl.start_of(2), Some(2.0));
|
||||
assert_eq!(tl.start_of(3), Some(2.25));
|
||||
assert_eq!(tl.start_of(4), None, "a triggered effect is not on the play-all clock");
|
||||
assert_eq!(tl.start_of(9), None);
|
||||
// Playing from the third effect: what came before has played, the rest has not.
|
||||
let at = tl.start_of(2).unwrap();
|
||||
assert!(tl.state_at(ShapeId(1), None, at).visible, "entered before");
|
||||
assert!(!tl.state_at(ShapeId(2), None, at).visible, "exited before");
|
||||
assert!(!tl.state_at(ShapeId(3), None, at).visible, "not entered yet");
|
||||
assert!(!tl.state_at(ShapeId(4), None, at).visible, "not entered yet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locate_play_all_clock() {
|
||||
let tl = Timeline::new(
|
||||
|
||||
@@ -359,6 +359,19 @@ impl Timeline {
|
||||
(self.main.len(), 0.0)
|
||||
}
|
||||
|
||||
/// Where effect `anim` (index into `slide.animations`) starts on the play-everything clock:
|
||||
/// Animation Pane ▸ Play From. `None` for a triggered effect or one that isn't on the slide.
|
||||
pub fn start_of(&self, anim: usize) -> Option<f64> {
|
||||
let e = self
|
||||
.effects
|
||||
.iter()
|
||||
.map(|e| &e.timing)
|
||||
.filter(|e| e.anim == anim && e.sequence == Sequence::Main)
|
||||
.min_by(|a, b| a.step.cmp(&b.step).then(a.start.total_cmp(&b.start)))?;
|
||||
let before: f64 = self.main.iter().take(e.step).map(|s| s.duration).sum();
|
||||
Some(before + e.start)
|
||||
}
|
||||
|
||||
/// Shapes that start an interactive sequence when clicked.
|
||||
pub fn trigger_shapes(&self) -> Vec<ShapeId> {
|
||||
self.triggers.iter().map(|(s, _)| *s).collect()
|
||||
|
||||
@@ -80,6 +80,88 @@ fn dragging_the_edge_sets_the_duration() {
|
||||
assert_eq!(timing(&h, 1), (0, 1000));
|
||||
}
|
||||
|
||||
/// Seconds on the play-all clock where the canvas preview is, right after the pane's play button.
|
||||
fn play(h: &mut Harness<'_, SlideApp>, label: &str) -> f64 {
|
||||
h.get_by_label(label).click();
|
||||
h.run_steps(2);
|
||||
let (_, at, _) = h.state().anim_preview_report().expect("preview running");
|
||||
h.state_mut().stop_anim_preview();
|
||||
h.run_steps(1);
|
||||
at
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn play_from_starts_at_the_selected_effect() {
|
||||
let mut h = harness();
|
||||
// The harness leaves the zoomed shape selected: its effect starts once the 1 s fade is over.
|
||||
let at = play(&mut h, "▶ Play From");
|
||||
assert!((1.0..1.1).contains(&at), "{at}");
|
||||
let (shape, tl) = {
|
||||
let st = h.state().session.active().unwrap();
|
||||
let slide = st.current_slide().unwrap();
|
||||
(slide.animations[0].shape, deckcraft_anim::Timeline::new(slide, st.doc.slide_size.width, st.doc.slide_size.height))
|
||||
};
|
||||
assert!(tl.state_at(shape, None, at).visible, "the faded-in shape is already on the slide");
|
||||
|
||||
h.state_mut().session.execute("edit.deselect", &json!({})).unwrap();
|
||||
h.run_steps(2);
|
||||
assert!(play(&mut h, "▶ Play All") < 0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn play_from_follows_the_effect_clicked_in_the_pane() {
|
||||
let mut h = harness();
|
||||
// A second effect on the first shape, after the zoom: 3 s into the sequence.
|
||||
let first = h.state().session.active().unwrap().current_slide().unwrap().animations[0].shape.0;
|
||||
h.state_mut().session.execute("edit.select", &json!({"ids": [first]})).unwrap();
|
||||
h.state_mut().session.execute("animation.add", &json!({"effect": "pulse", "start": "afterPrevious", "duration": 1000})).unwrap();
|
||||
h.run_steps(2);
|
||||
// The row of the pulse, left of its bar.
|
||||
let bar = h.get_by_label("Timing bar 3").rect();
|
||||
let row = egui::pos2(bar.left() - 20.0, bar.center().y);
|
||||
h.hover_at(row);
|
||||
h.run_steps(1);
|
||||
h.drag_at(row);
|
||||
h.run_steps(1);
|
||||
h.drop_at(row);
|
||||
h.run_steps(2);
|
||||
let at = play(&mut h, "▶ Play From");
|
||||
assert!((3.0..3.1).contains(&at), "plays from the clicked row, not the shape's first effect: {at}");
|
||||
}
|
||||
|
||||
/// Clicks the pane row of effect `n` (1-based), left of its timing bar, with `button`.
|
||||
fn click_row(h: &mut Harness<'_, SlideApp>, n: usize, button: egui::PointerButton) {
|
||||
let bar = h.get_by_label(&format!("Timing bar {n}")).rect();
|
||||
let pos = egui::pos2(bar.left() - 20.0, bar.center().y);
|
||||
h.event(egui::Event::PointerMoved(pos));
|
||||
h.run_steps(1);
|
||||
for pressed in [true, false] {
|
||||
h.event(egui::Event::PointerButton { pos, button, pressed, modifiers: egui::Modifiers::default() });
|
||||
h.run_steps(1);
|
||||
}
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_an_effect_forgets_the_clicked_row() {
|
||||
let mut h = harness();
|
||||
let first = h.state().session.active().unwrap().current_slide().unwrap().animations[0].shape.0;
|
||||
h.state_mut().session.execute("edit.select", &json!({"ids": [first]})).unwrap();
|
||||
for effect in ["pulse", "spin"] {
|
||||
h.state_mut().session.execute("animation.add", &json!({"effect": effect, "start": "afterPrevious", "duration": 1000})).unwrap();
|
||||
}
|
||||
h.run_steps(2);
|
||||
// Fade, zoom, pulse, spin: pick the pulse, then move it below the spin.
|
||||
click_row(&mut h, 3, egui::PointerButton::Primary);
|
||||
click_row(&mut h, 3, egui::PointerButton::Secondary);
|
||||
h.get_by_label("Move Down").click();
|
||||
h.run_steps(3);
|
||||
let effects: Vec<String> = h.state().session.active().unwrap().current_slide().unwrap().animations.iter().map(|a| a.effect.clone()).collect();
|
||||
assert_eq!(effects[2..], ["spin", "pulse"]);
|
||||
let at = play(&mut h, "▶ Play From");
|
||||
assert!(at < 0.1, "back to the shape's first effect, not the spin now in that row: {at}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "writes a screenshot for review"]
|
||||
fn screenshot() {
|
||||
|
||||
@@ -382,7 +382,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
||||
("show.start", "Slide Show", Some("F5"), "{from?: index, custom?: name}"),
|
||||
("show.presenter", "Presenter View", None, "{on?: bool}"),
|
||||
("show.end", "End Show", None, "{}"),
|
||||
("animation.preview", "Preview Animations", None, "{on?: bool}"),
|
||||
("animation.preview", "Preview Animations", None, "{on?: bool, from?: effect index}"),
|
||||
("app.home", "Home", None, "{on?}"),
|
||||
("app.backstage", "File", Some("Alt+F"), "{page?: home|new|open|info|saveAs|print|export|account, on?: bool}"),
|
||||
("app.closePresentation", "Close", Some("Cmd+W"), "{}"),
|
||||
@@ -428,6 +428,8 @@ struct AnimPreview {
|
||||
slide_ptr: usize,
|
||||
/// egui time when the first frame latched the clock (`None` until then).
|
||||
started: Option<f64>,
|
||||
/// Where on the play-everything clock playback began (Play From the selected effect).
|
||||
from: f64,
|
||||
elapsed: f64,
|
||||
timeline: deckcraft_anim::Timeline,
|
||||
}
|
||||
@@ -969,7 +971,8 @@ impl SlideApp {
|
||||
self.anim_preview = None;
|
||||
return Ok(json!({"playing": false}));
|
||||
}
|
||||
return self.start_anim_preview();
|
||||
let from = p.get("from").and_then(Value::as_u64).map(|v| usize::try_from(v).unwrap_or(usize::MAX));
|
||||
return self.start_anim_preview(from);
|
||||
}
|
||||
"app.home" => {
|
||||
let on = self.flag(p, self.backstage_open());
|
||||
@@ -1129,7 +1132,7 @@ impl SlideApp {
|
||||
let Some(p) = self.anim_preview.as_mut() else { return false };
|
||||
let now = if now.is_finite() { now } else { p.started.unwrap_or(0.0) };
|
||||
let start = *p.started.get_or_insert(now);
|
||||
let elapsed = (now - start).max(0.0);
|
||||
let elapsed = p.from + (now - start).max(0.0);
|
||||
if !elapsed.is_finite() || elapsed > p.timeline.total_duration() + preview::HOLD_SECS {
|
||||
self.anim_preview = None;
|
||||
return false;
|
||||
@@ -1138,7 +1141,9 @@ impl SlideApp {
|
||||
true
|
||||
}
|
||||
|
||||
fn start_anim_preview(&mut self) -> Result<Value, String> {
|
||||
/// Play the current slide's animations on the canvas, from effect `from` (index into the slide's
|
||||
/// animations) or from the start.
|
||||
fn start_anim_preview(&mut self, from: Option<usize>) -> Result<Value, String> {
|
||||
let (uid, slide, ptr, timeline) = {
|
||||
let Some(d) = self.session.active() else {
|
||||
return Err("no presentation is open".into());
|
||||
@@ -1159,14 +1164,18 @@ impl SlideApp {
|
||||
self.set_status("This slide has no animations");
|
||||
return Ok(json!({"playing": false}));
|
||||
}
|
||||
let from_secs = match from {
|
||||
Some(i) => timeline.start_of(i).ok_or_else(|| format!("no effect {i} in this slide's click sequence"))?,
|
||||
None => 0.0,
|
||||
};
|
||||
if matches!(self.ui.view, ViewMode::Sorter | ViewMode::NotesPage) {
|
||||
self.ui.view = ViewMode::Normal;
|
||||
}
|
||||
self.close_backstage();
|
||||
let duration = timeline.total_duration();
|
||||
self.anim_preview = Some(AnimPreview { uid, slide, slide_ptr: ptr, started: None, elapsed: 0.0, timeline });
|
||||
self.anim_preview = Some(AnimPreview { uid, slide, slide_ptr: ptr, started: None, from: from_secs, elapsed: from_secs, timeline });
|
||||
self.set_status("Previewing animations");
|
||||
Ok(json!({"playing": true, "slide": slide, "duration": duration}))
|
||||
Ok(json!({"playing": true, "slide": slide, "duration": duration, "from": from_secs}))
|
||||
}
|
||||
|
||||
fn pick_and_open(&mut self) {
|
||||
|
||||
@@ -1013,13 +1013,32 @@ fn animation(app: &mut SlideApp, ui: &mut Ui) {
|
||||
let Some(slide) = st.current_slide() else { return };
|
||||
let anims = slide.animations.clone();
|
||||
let size = (st.doc.slide_size.width, st.doc.slide_size.height);
|
||||
let effects = deckcraft_anim::Timeline::new(slide, size.0, size.1).effects();
|
||||
let tl = deckcraft_anim::Timeline::new(slide, size.0, size.1);
|
||||
let effects = tl.effects();
|
||||
let names: Vec<(u32, String)> = st.shapes().iter().map(|s| (s.id.0, s.name.clone())).collect();
|
||||
let selected: Vec<u32> = st.selection.shapes.iter().map(|i| i.0).collect();
|
||||
// Play From starts at the effect last clicked here, or else the first effect of the selected
|
||||
// shape; with nothing selected it is Play All. Reordering or removing forgets the click: the
|
||||
// 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());
|
||||
ui.horizontal(|ui| {
|
||||
let playing = app.anim_preview_active();
|
||||
if ui.button(if playing { "■ Stop" } else { "▶ Play All" }).clicked() {
|
||||
run(app, "animation.preview", json!({"on": !playing}));
|
||||
let label = match (playing, from) {
|
||||
(true, _) => "■ Stop",
|
||||
(false, Some(_)) => "▶ Play From",
|
||||
(false, None) => "▶ Play All",
|
||||
};
|
||||
if ui.button(label).clicked() {
|
||||
let p = match (playing, from) {
|
||||
(false, Some(i)) => json!({"on": true, "from": i}),
|
||||
_ => json!({"on": !playing}),
|
||||
};
|
||||
run(app, "animation.preview", p);
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
@@ -1042,6 +1061,9 @@ fn animation(app: &mut SlideApp, ui: &mut Ui) {
|
||||
if sel || resp.hovered() {
|
||||
ui.painter().rect_filled(r, CornerRadius::same(4), if sel { t.selected } else { t.hover });
|
||||
}
|
||||
if from == Some(i) {
|
||||
ui.painter().rect_stroke(r, CornerRadius::same(4), Stroke::new(1.5, t.accent), egui::StrokeKind::Inside);
|
||||
}
|
||||
let p = ui.painter();
|
||||
p.text(
|
||||
pos2(r.min.x + 6.0, r.center().y),
|
||||
@@ -1123,6 +1145,7 @@ fn animation(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
if resp.clicked() {
|
||||
ui.data_mut(|m| m.insert_temp(pick_id, i));
|
||||
run(app, "edit.select", json!({"ids": [a.shape.0]}));
|
||||
}
|
||||
resp.context_menu(|ui| {
|
||||
@@ -1134,14 +1157,17 @@ fn animation(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Move Up").clicked() {
|
||||
ui.data_mut(|m| m.remove::<usize>(pick_id));
|
||||
run(app, "animation.move", json!({"index": i, "to": i.saturating_sub(1)}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Move Down").clicked() {
|
||||
ui.data_mut(|m| m.remove::<usize>(pick_id));
|
||||
run(app, "animation.move", json!({"index": i, "to": i + 1}));
|
||||
ui.close();
|
||||
}
|
||||
if ui.button("Remove").clicked() {
|
||||
ui.data_mut(|m| m.remove::<usize>(pick_id));
|
||||
run(app, "animation.remove", json!({"index": i}));
|
||||
ui.close();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Transitions and animations: parity with PowerPoint
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (community PR wave 2026-10-14: interactive-deck triggers, canvas preview, the Animation Pane timeline, repeat and bounce end round-trip; duplicated rows and headers folded; previous: major, transitions measured frame by frame against PowerPoint for Windows' video export) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** minor (Animation Pane ▸ Play From plays from the selected effect; previous: community PR wave 2026-10-14: interactive-deck triggers, canvas preview, the Animation Pane timeline, repeat and bounce end round-trip; duplicated rows and headers folded; previous: major, transitions measured frame by frame against PowerPoint for Windows' video export) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
Transitions and animations are what make a deck a presentation, and the part of PowerPoint people
|
||||
notice first when it's missing. Scores roll up into [target-app-parity.md](target-app-parity.md)
|
||||
@@ -73,7 +73,7 @@ In, Wipe, Zoom, Float In, Grow/Shrink, Pulse, Spin, motion lines): weighted, ~85
|
||||
| Animation Pane: list, reorder, remove | yes | yes |
|
||||
| Animation Pane: advanced timeline (bars you drag) | yes | yes: bars from the show schedule, drag the body for delay, the right edge for duration, seconds ruler (`animpane_tests`) |
|
||||
| Preview on the editor canvas (the current slide) | yes | yes |
|
||||
| Preview from the selected effect | yes | no |
|
||||
| Preview from the selected effect | yes | yes: with an effect selected the pane's button reads Play From and starts at that effect, earlier effects already played (`animpane_tests`) |
|
||||
| Motion path editing (edit points, reverse, lock) | yes | partly (no edit points) |
|
||||
| Multiple animations on one object | yes | yes |
|
||||
| Round trip of the timing tree | yes | flattened to the pane list on read, rebuilt on write (`every_animation_effect_survives`) |
|
||||
@@ -82,18 +82,19 @@ In, Wipe, Zoom, Float In, Grow/Shrink, Pulse, Spin, motion lines): weighted, ~85
|
||||
|
||||
| Work | Hours |
|
||||
|---|---|
|
||||
| Preview from selected, effect dialog depth | 4–6 |
|
||||
| Effect dialog depth | 3–5 |
|
||||
| The remaining ~126 effects (most are parameter variations of existing ones) | 3–6 |
|
||||
| Rewind and other effect options on round trip (#77) | 1–2 |
|
||||
| Transition sounds: the rest of PowerPoint's built-in list (Applause, Arrow, Bomb…) | 1 |
|
||||
| Morph across pictures, `!!` names, align once per transition (#67) | 2–4 |
|
||||
| Cloth, paper and particle transitions as real meshes (Curtains, Peel Off, Page Curl, Airplane, Origami, Vortex, Shred, Honeycomb) | 4–8 |
|
||||
| **Total** | **~15–27** (the Transitions and Animations rows of the feature table; part of the Features dimension's 140–240 h) |
|
||||
| **Total** | **~14–26** (the Transitions and Animations rows of the feature table; part of the Features dimension's 140–240 h) |
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-22 | minor | Animation Pane ▸ Play From: with an effect selected, the preview starts at that effect on the play-all clock; the row says yes and the estimate drops to ~14–26 h |
|
||||
| 2026-10-15 | trivial | PPTX interactive sequences preserve later On Click effects and one click per paragraph; no change to playback or feature scores |
|
||||
| 2026-10-14 | trivial | Community PR wave folded: the Animation Pane timeline row says yes (it landed), preview split into canvas (yes) and from the selected effect (no); estimates drop the timeline and transition sounds (~15–27 h) |
|
||||
| 2026-10-14 | trivial | Repeat and bounce end round-trip through PPTX; rewind is still lost (#77) |
|
||||
|
||||
+7
-5
@@ -1,6 +1,6 @@
|
||||
# Where DeckCraft falls short of PowerPoint
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-18 · **Change:** minor (native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: soak tests, PPTX corpus, comment threads (#70), embedded fonts, SmartArt Text Pane, Animation Pane timeline, Hunspell spelling, thesaurus, AutoCorrect, 14 new dialogs, pen pressure and trackpad gestures; duplicated entries and headers folded; previous: minor, 3-D bevel, camera and face lighting rendered) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** minor (Animation Pane ▸ Play From; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: soak tests, PPTX corpus, comment threads (#70), embedded fonts, SmartArt Text Pane, Animation Pane timeline, Hunspell spelling, thesaurus, AutoCorrect, 14 new dialogs, pen pressure and trackpad gestures; duplicated entries and headers folded; previous: minor, 3-D bevel, camera and face lighting rendered) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
Every known shortfall, one entry each, ranked by how much it stops someone who presents with
|
||||
PowerPoint from switching. This is the work list: pick from the top. Numbers are summarized in
|
||||
@@ -26,7 +26,7 @@ revision history.
|
||||
| 7 | Fonts: only Regular, Bold and Italic faces (#69) | Feature | B | 3–5 | [ui-parity.md](ui-parity.md) |
|
||||
| 8 | Image paste fails on Windows and Linux (#53) | Feature | B | 2–4 | [ui-parity.md](ui-parity.md) |
|
||||
| 9 | Presenter view isn't placed on the second display | Hardware | B | 3–5 | [slideshow-parity.md](slideshow-parity.md) |
|
||||
| 10 | Animation Pane: preview plays the whole slide, not from the selected effect (timeline done); 64 of ~190 effects | Feature | B (pane) | 8–14 | [animation-parity.md](animation-parity.md) |
|
||||
| 10 | Animation Pane: 64 of ~190 effects, effect options (timeline and Play From done) | Feature | B (pane) | 7–13 | [animation-parity.md](animation-parity.md) |
|
||||
| 11 | Charts: data grid editing, elements, `chartex` types | Feature | B (data, elements) | 12–20 | [parity-checklist.md](parity-checklist.md) |
|
||||
| 12 | Format Shape pane thinner than PowerPoint's | UI/UX | B | 6–10 | [ui-parity.md](ui-parity.md) |
|
||||
| 13 | No native macOS menu bar | UI/UX | B | 3–5 | [ui-parity.md](ui-parity.md) |
|
||||
@@ -202,9 +202,10 @@ Detail in [file-format-parity.md](file-format-parity.md).
|
||||
|
||||
### Animation Pane, effect count and preview
|
||||
Animations ▸ Preview and the pane's Play All play the current slide's animations on the editor
|
||||
canvas (Escape or a click stops them), and the pane has an advanced timeline with draggable bars
|
||||
(`crates/ui-egui/src/animtimeline.rs`). Still open: previewing from the selected effect, and most
|
||||
of the effect gallery. Detail in [animation-parity.md](animation-parity.md). 8–14 h; the pane is a
|
||||
canvas (Escape or a click stops them); with an effect selected the pane's button reads Play From
|
||||
and starts at that effect. The pane has an advanced timeline with draggable bars
|
||||
(`crates/ui-egui/src/animtimeline.rs`). Still open: most of the effect gallery. Detail in
|
||||
[animation-parity.md](animation-parity.md). 7–13 h; the pane is a
|
||||
**beta blocker**.
|
||||
|
||||
### Interactive decks: media-action links, audio-triggered effects and click boxes
|
||||
@@ -356,6 +357,7 @@ Detail in [localization-parity.md](localization-parity.md).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-22 | minor | Animation Pane ▸ Play From starts the preview at the selected effect; gap 10 keeps the effect gallery (7–13 h) |
|
||||
| 2026-10-18 | minor | Native chart payloads use referenced ZIP members and share media byte/item limits; old inline sidecars remain readable. Synthetic tests cover multi-megabyte layout, all shape owners, shared budgets, missing/corrupt members and invalid references. Scores and milestone scope unchanged |
|
||||
| 2026-10-15 | trivial | Native saves retain chart package parts and relationships, including charts in groups; edited chart data still does not export a stale source workbook. Chart editing gaps and scores remain unchanged |
|
||||
| 2026-10-15 | trivial | PPTX trigger sequences keep later On Click steps instead of converting them to After Previous; mixed starts, independent buttons and paragraph builds have synthetic round-trip regressions |
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# DeckCraft roadmap: milestones and what's next
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-18 · **Change:** minor (native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: PPTX corpus, presenter and show controls, review navigation, starter decks, Grid Options and New Window, WordArt gallery and handles, pen pressure; beta items 9 and 10 narrowed; duplicated rows and headers folded; previous: minor, core-workflow alpha gate added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** trivial (beta item 9 drops Play From, now in; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: PPTX corpus, presenter and show controls, review navigation, starter decks, Grid Options and New Window, WordArt gallery and handles, pen pressure; beta items 9 and 10 narrowed; duplicated rows and headers folded; previous: minor, core-workflow alpha gate added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
Forward-looking: where DeckCraft is going and in what order. The one-page summary is
|
||||
[ROADMAP.md](../ROADMAP.md); every shortfall is in [gaps.md](gaps.md); numbers are in
|
||||
@@ -52,7 +52,7 @@ Beta means ~75% ready for real work and opening and saving PowerPoint's `.pptx`
|
||||
| 7 | Font weights (#69), image paste (#53), Tab in text (#78) | 6–11 | Features |
|
||||
| 8 | Charts: data grid, elements | 6–10 | Features |
|
||||
| 10 | Presenter view on the right display, swap displays | 2–4 | Hardware, Slide show |
|
||||
| 9 | Animation Pane: preview from the selected effect, effect options (the timeline is in) | 4–7 | Features |
|
||||
| 9 | Animation Pane: effect options (the timeline and Play From are in) | 3–6 | Features |
|
||||
| 11 | Native macOS menu bar, Format Shape pane depth, start screen and window chrome fixes | 13–22 | UI/UX 60 → 75% |
|
||||
| 12 | Performance baseline and the obvious fixes | 4–8 | Performance |
|
||||
| **Total** | | **~120–210** | Ready ~55% → ~75% |
|
||||
@@ -115,6 +115,7 @@ plan above.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-22 | trivial | Beta item 9: Animation Pane ▸ Play From landed, the item keeps effect options (3–6 h) |
|
||||
| 2026-10-18 | minor | Native chart payloads use referenced ZIP members and share media byte/item limits; old inline sidecars remain readable. Synthetic tests cover multi-megabyte layout, all shape owners, shared budgets, missing/corrupt members and invalid references. Scores and milestone scope unchanged |
|
||||
| 2026-10-15 | trivial | Native save/reopen retains imported chart workbooks and related parts; headless command tests cover a PPTX → native → PPTX cycle and stale-workbook rejection. Milestone scope and scores unchanged |
|
||||
| 2026-10-15 | trivial | PPTX trigger click steps survive save, including by-paragraph builds. Interactive-deck gaps remain media actions, audio triggers and click-box geometry; milestone scope and feature scores unchanged |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# DeckCraft vs Microsoft PowerPoint: parity assessment
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** major (community PR wave 2026-10-14: breadth 84% over 191 features (P0 92%, P1 82%, P2 55%) after `cargo xtask parity`; area table and What works today folded to one row or bullet each; 320 engine commands; ready ~55% and stage alpha unchanged; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** trivial (Animations shortfall: preview from the selected effect is in; previous: major, community PR wave 2026-10-14: breadth 84% over 191 features (P0 92%, P1 82%, P2 55%) after `cargo xtask parity`; area table and What works today folded to one row or bullet each; 320 engine commands; ready ~55% and stage alpha unchanged; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
The authoritative answer to "how close is DeckCraft to PowerPoint, and how much work is left?".
|
||||
[ROADMAP.md](../ROADMAP.md) summarizes it; [gaps.md](gaps.md) itemizes every shortfall;
|
||||
@@ -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, preview from the selected effect, rewind lost on save (#77) | [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) |
|
||||
| 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) |
|
||||
@@ -289,6 +289,7 @@ scored done (16 open issues).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-22 | trivial | Animations shortfall: preview from the selected effect (Play From) is in. Percentages unchanged |
|
||||
| 2026-10-16 | trivial | Arrange shortfall: Align to Slide and Align Selected Objects are in the Align menu. Percentages unchanged |
|
||||
| 2026-10-14 | minor | Community PR wave folded: one headline breadth row (84% over 191 features after `cargo xtask parity`; duplicate checklist rows removed), one row per feature area with the latest breadth and shortfalls, one Themes / Slide show / Views / Review / Automation bullet each (320 engine commands). SmartArt ready ~35% (Text Pane, 6e40335) moves the area mean to 62.8%; dimension and headline percentages unchanged |
|
||||
| 2026-10-13 | minor | Shapes shortfall "text warp" narrowed to adjust handles: the Transform gallery applies a preset |
|
||||
|
||||
Reference in New Issue
Block a user