Merge PR #656 from NikitkaRa: Presenter view: pause and reset the timer, and notes size

# Conflicts:
#	crates/ui-egui/src/show.rs
This commit is contained in:
Brandon Thomas
2026-10-14 13:48:31 -04:00
7 changed files with 190 additions and 23 deletions
+2
View File
@@ -180,6 +180,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-13 | Playback stays with its document and its clip (#415): switching decks or closing the last one stops the previous sound, replacing a clip plays the new one, and the duration and decode caches keep the bytes they are keyed by |
| 2026-10-13 | Slide show: a laser pointer (Cmd/Ctrl+L, the show's laser colour, no ink). Checklist row Partial; weighted breadth stays 79%, P2 summary 39%, slide show area 68%. The presenter window does not move the dot |
| 2026-10-14 | Slide Show: Show Media Controls writes the show setting and hides the playback bar and GIF button during the show |
| 2026-10-14 | Presenter view pauses and resets its timer (T, R) and changes notes size (A-/A+, brackets). The clock shows local time. Second-display placement stays with #433. Breadth stays 79% |
| 2026-10-14 | See All Slides and zoom during a show: a thumbnail grid (G, or All), click to jump, and click-to-zoom at 2× (Z). Presenter view has Black, White, All slides and Zoom. Breadth stays 79% |
| 2026-10-13 | Slide transitions like PowerPoint: every transition and option measured frame by frame against PowerPoint's video export and retuned (curves, directions, soft edges, 3-D perspective, contents moving over backgrounds); PowerPoint's own XML elements and defaults read and written (diagonal Wipe, Zoom, Shape In/Out, Glitter Hexagons, Shred In/Out, Page Curl Single); `deckcraft-cli render-transition` renders frames headless |
| 2026-10-13 | Wild text cases measured against PowerPoint (102 synthetic slides): bidi by run language, East Asian vertical text upright, mongolianVert/wordArtVert/wordArtVertRtl, underline styles and strikethrough at the font's metrics, centre/right/decimal tabs, East Asian, Thai, Hindi, Arabic and Hebrew numbering, small caps, all-caps "ß", transparent text, soft hyphens, kinsoku, rtlCol and just/dist anchoring, emoji presentation, fallback faces that no longer move the baseline |
@@ -229,6 +230,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-13 | trivial | Progress log: media playback follows the open document and clip (#415) |
| 2026-10-13 | trivial | Progress log: laser pointer. Headline P2 36% → 39% to match the checklist (the row moves P2 unweighted 38% → 39%; breadth stays 79%) |
| 2026-10-14 | trivial | Progress log: Show Media Controls |
| 2026-10-14 | trivial | Progress log: presenter timer pause/reset, clock and notes size. Breadth stays 79% |
| 2026-10-14 | trivial | Progress log: See All Slides and zoom during a show. Breadth stays 79% |
| 2026-10-13 | trivial | Progress log: wild text cases |
| 2026-10-13 | trivial | Progress log: slide show for interactive decks |
+2 -1
View File
@@ -80,7 +80,8 @@ pub fn inspect(app: &SlideApp, ctx: &egui::Context) -> Value {
let h = d.doc.slide_size.height.max(1.0) as f32;
(d.doc.slides.len(), d.doc.slide_size.width as f32 / h)
}).unwrap_or((0, 16.0 / 9.0));
s.showing_value(n, aspect, ctx.content_rect())
let now = ctx.input(|i| i.time);
s.showing_value(n, aspect, ctx.content_rect(), now)
}),
"documents": app.session.documents().iter().map(|d| json!({"title": d.title(), "dirty": d.is_dirty(), "path": d.path})).collect::<Vec<_>>(),
"status": app.status.as_ref().map(|s| s.0.clone()),
+167 -14
View File
@@ -45,6 +45,10 @@ pub struct Show {
goto: String,
started: bool,
show_start: f64,
/// Elapsed time in presenter view. Pausing it does not pause the show.
timer: PresenterTimer,
/// Speaker-notes size in presenter view, in points.
notes_px: f32,
timings: Vec<(usize, f64)>,
last_move: f64,
pub ended: bool,
@@ -188,6 +192,8 @@ impl Show {
goto: String::new(),
started: false,
show_start: 0.0,
timer: PresenterTimer::start(0.0),
notes_px: NOTES_DEFAULT,
timings: vec![],
last_move: 0.0,
ended: false,
@@ -456,6 +462,10 @@ fn keyboard(ctx: &egui::Context, show: &mut Show, doc: &Presentation, now: f64)
E => show.strokes.clear(),
G if plain(m) => show.grid = true,
Z if plain(m) => show.zoom_key(),
T if plain(m) => show.timer.toggle(now),
R if plain(m) => show.timer.reset(now),
OpenBracket if plain(m) => show.nudge_notes(-NOTES_STEP),
CloseBracket if plain(m) => show.nudge_notes(NOTES_STEP),
Num0 | Num1 | Num2 | Num3 | Num4 | Num5 | Num6 | Num7 | Num8 | Num9 => {
show.goto.push_str(k.name());
}
@@ -488,6 +498,7 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
show.slide_start = now;
show.step_start = now;
show.show_start = now;
show.timer = PresenterTimer::start(now);
show.last_move = now;
if !show.reading {
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(true));
@@ -1288,7 +1299,14 @@ impl Show {
self.zoom.map(|z| (z.nx, z.ny, z.scale))
}
pub fn showing_value(&self, n_slides: usize, aspect: f32, full: Rect) -> serde_json::Value {
fn nudge_notes(&mut self, delta: f32) {
let next = self.notes_px + delta;
if next.is_finite() {
self.notes_px = next.clamp(NOTES_MIN, NOTES_MAX);
}
}
pub fn showing_value(&self, n_slides: usize, aspect: f32, full: Rect, now: f64) -> serde_json::Value {
let mut v = json!({
"slide": self.state.slide,
"step": self.state.step,
@@ -1297,6 +1315,9 @@ impl Show {
"zoomArm": self.zoom_arm,
"blank": self.blank_name(),
"zoom": self.zoom_view().map(|(x, y, scale)| json!({"x": x, "y": y, "scale": scale})),
"presenterPaused": self.timer.paused(),
"presenterSecs": self.timer.elapsed(now),
"notesPx": self.notes_px,
});
if self.grid {
let slides = grid_indices(n_slides);
@@ -1319,6 +1340,74 @@ impl Show {
const ZOOM_SCALE: f32 = 2.0;
const GRID_CAP: usize = 48;
const TOOL_BAR_W: f32 = 520.0;
const NOTES_DEFAULT: f32 = 18.0;
const NOTES_MIN: f32 = 12.0;
const NOTES_MAX: f32 = 48.0;
const NOTES_STEP: f32 = 4.0;
/// Presenter-view elapsed time. Pausing freezes the number; the show keeps playing.
#[derive(Clone, Copy)]
struct PresenterTimer {
origin: f64,
frozen: f64,
paused: bool,
}
impl PresenterTimer {
fn start(now: f64) -> Self {
PresenterTimer { origin: finite_time(now), frozen: 0.0, paused: false }
}
fn elapsed(self, now: f64) -> f64 {
if self.paused {
return finite_secs(self.frozen);
}
let dt = now - self.origin;
finite_secs(self.frozen + if dt.is_finite() && dt > 0.0 { dt } else { 0.0 })
}
fn paused(self) -> bool {
self.paused
}
fn toggle(&mut self, now: f64) {
if self.paused {
self.origin = finite_time(now);
self.paused = false;
} else {
self.frozen = self.elapsed(now);
self.paused = true;
}
}
/// Zero the elapsed time. A paused timer stays paused at zero.
fn reset(&mut self, now: f64) {
self.frozen = 0.0;
self.origin = finite_time(now);
}
}
fn finite_time(now: f64) -> f64 {
if now.is_finite() { now } else { 0.0 }
}
fn finite_secs(n: f64) -> f64 {
if n.is_finite() && n >= 0.0 { n } else { 0.0 }
}
fn format_elapsed(secs: f64) -> String {
let s = finite_secs(secs) as u64;
let s = s.min(99 * 3600 + 59 * 60 + 59);
format!("{:02}:{:02}:{:02}", s / 3600, (s / 60) % 60, s % 60)
}
/// Local wall clock for presenter view. `--:--` where there is no clock (wasm).
fn wall_clock() -> String {
match deckcraft_text::datetime::DateTime::now_local() {
Some(t) if t.hour < 24 && t.minute < 60 => format!("{:02}:{:02}", t.hour, t.minute),
_ => "--:--".to_string(),
}
}
#[derive(Clone, Copy)]
struct Zoom {
@@ -1552,7 +1641,9 @@ fn presenter_window(ctx: &egui::Context, show: &mut Show, doc: &Presentation, no
}
let next_tex = show.next_tex.as_ref().filter(|(k, _)| Some(*k) == next).map(|(_, t)| t.clone());
let notes = doc.slides.get(cur).map(|s| s.notes_text()).unwrap_or_default();
let elapsed = now - show.show_start;
let clock = wall_clock();
// The presenter clock only moves when this window is drawn again.
ctx.request_repaint_after(std::time::Duration::from_millis(500));
let mut actions = vec![];
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("deckcraft_presenter"),
@@ -1561,18 +1652,18 @@ fn presenter_window(ctx: &egui::Context, show: &mut Show, doc: &Presentation, no
if !keyboard(ui.ctx(), show, doc, now) {
*keep = false;
}
ui.ctx().request_repaint_after(std::time::Duration::from_millis(500));
let elapsed = show.timer.elapsed(now);
let paused = show.timer.paused();
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(Color32::from_gray(24)).inner_margin(egui::Margin::same(16))).show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(
egui::RichText::new(format!(
"{:02}:{:02}:{:02}",
(elapsed / 3600.0) as u32,
(elapsed / 60.0) as u32 % 60,
elapsed as u32 % 60
))
.size(22.0)
.color(Color32::WHITE),
);
ui.label(egui::RichText::new(format_elapsed(elapsed)).size(22.0).color(if paused {
Color32::from_gray(160)
} else {
Color32::WHITE
}));
ui.add_space(12.0);
ui.label(egui::RichText::new(clock.clone()).size(16.0).color(Color32::from_gray(200)));
ui.add_space(20.0);
ui.label(egui::RichText::new(format!("Slide {} of {}", cur + 1, doc.slides.len())).size(16.0).color(Color32::from_gray(200)));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
@@ -1588,6 +1679,12 @@ fn presenter_window(ctx: &egui::Context, show: &mut Show, doc: &Presentation, no
});
});
ui.horizontal(|ui| {
if ui.button(if paused { "Resume" } else { "Pause" }).clicked() {
show.timer.toggle(now);
}
if ui.button("Reset").clicked() {
show.timer.reset(now);
}
if ui.button(if show.blank == Some(Color32::BLACK) { "Black on" } else { "Black" }).clicked() {
toggle_blank(show, Color32::BLACK);
}
@@ -1642,10 +1739,24 @@ fn presenter_window(ctx: &egui::Context, show: &mut Show, doc: &Presentation, no
ui.painter().text(nr.center(), Align2::CENTER_CENTER, "End of slide show", theme::font(13.0), Color32::from_gray(170));
}
}
let notes_rect = Rect::from_min_max(pos2(right.min.x, nr.max.y + 16.0), right.max);
let notes_head = Rect::from_min_size(pos2(right.min.x, nr.max.y + 12.0), vec2(right.width(), 28.0));
let mut head = ui.new_child(egui::UiBuilder::new().max_rect(notes_head));
head.horizontal(|ui| {
ui.label(egui::RichText::new("Notes").size(13.0).color(Color32::from_gray(170)));
if ui.button("A-").clicked() {
show.nudge_notes(-NOTES_STEP);
}
ui.label(egui::RichText::new(format!("{}", show.notes_px as i32)).size(13.0).color(Color32::from_gray(200)));
if ui.button("A+").clicked() {
show.nudge_notes(NOTES_STEP);
}
});
let notes_rect = Rect::from_min_max(pos2(right.min.x, notes_head.max.y + 4.0), right.max);
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(notes_rect));
egui::ScrollArea::vertical().show(&mut child, |ui| {
ui.label(egui::RichText::new(if notes.is_empty() { "No notes." } else { &notes }).size(18.0).color(Color32::from_gray(230)));
ui.label(
egui::RichText::new(if notes.is_empty() { "No notes." } else { &notes }).size(show.notes_px).color(Color32::from_gray(230)),
);
});
});
if ui.input(|i| i.viewport().close_requested()) {
@@ -1943,4 +2054,46 @@ mod tests {
assert!(show.apply(ShowAction::Slide(1), &doc, 1.0));
assert!(show.zoom.is_none(), "a new slide is shown whole");
}
#[test]
fn presenter_timer_pauses_resets_and_notes_size_clamps() {
let mut t = PresenterTimer::start(10.0);
assert!((t.elapsed(12.5) - 2.5).abs() < 1e-9);
t.toggle(15.0);
assert!(t.paused());
assert!((t.elapsed(100.0) - 5.0).abs() < 1e-9);
t.reset(100.0);
assert!(t.paused());
assert_eq!(t.elapsed(200.0), 0.0);
t.toggle(200.0);
assert!(!t.paused());
assert!((t.elapsed(203.0) - 3.0).abs() < 1e-9);
t.reset(203.0);
assert!(!t.paused());
assert!((t.elapsed(204.0) - 1.0).abs() < 1e-9);
let bad = PresenterTimer::start(f64::NAN);
assert_eq!(bad.elapsed(f64::INFINITY), 0.0);
assert_eq!(format_elapsed(3661.4), "01:01:01");
assert_eq!(format_elapsed(-1.0), "00:00:00");
assert_eq!(format_elapsed(f64::NAN), "00:00:00");
let clock = wall_clock();
assert!(clock == "--:--" || (clock.len() == 5 && clock.as_bytes().get(2) == Some(&b':')));
let mut s = deckcraft_engine::Session::with_new();
s.execute("slide.new", &json!({})).unwrap();
let doc = s.active().unwrap().doc.clone();
let mut show = Show::new(&doc, 0, false, true);
assert_eq!(show.notes_px, NOTES_DEFAULT);
show.nudge_notes(NOTES_STEP);
assert_eq!(show.notes_px, 22.0);
show.nudge_notes(1000.0);
assert_eq!(show.notes_px, NOTES_MAX);
show.nudge_notes(-1000.0);
assert_eq!(show.notes_px, NOTES_MIN);
show.nudge_notes(f32::NAN);
assert_eq!(show.notes_px, NOTES_MIN);
let v = show.showing_value(1, 16.0 / 9.0, Rect::from_min_size(Pos2::ZERO, vec2(100.0, 100.0)), 0.0);
assert_eq!(v["presenterPaused"], json!(false));
assert_eq!(v["notesPx"], json!(NOTES_MIN));
}
}
+2 -2
View File
@@ -15,12 +15,12 @@ Coordinates are slide points (1/72 inch; a 16:9 slide is 960 × 540) unless note
| `engine.execute` / `ui.menu.invoke` / `command` | `{command, params}` | Run any command (see `engine.commands`; `deckcraft-cli describe ID` documents one) |
| `engine.commands` | — | Every command: id, label, menu path, shortcut, params doc, enabled / disabled reason |
| `document.inspect` | — | Slides, sections, layouts, shapes, selection |
| `ui.inspect` | — | UI state, active tool, window/canvas/slide rects, dialog, `home` (the File tab open over a presentation), `backstage` (its page, also the start screen), slide show state (`grid`, `zoomArm`, `zoom`, `blank`; `cells` while See All Slides is open), documents, perf (fps, render ms) |
| `ui.inspect` | — | UI state, active tool, window/canvas/slide rects, dialog, `home` (the File tab open over a presentation), `backstage` (its page, also the start screen), slide show state (`grid`, `zoomArm`, `zoom`, `blank`, `presenterPaused`, `presenterSecs`, `notesPx`; `cells` while See All Slides is open), documents, perf (fps, render ms) |
| `ui.tool.select` | `{tool, preset?}` | `select`, `text`, `shape` + preset, any preset name, `curve`, `freeform`, `scribble`, `pen`, `highlighter`, `eraser` |
| `ui.pointer` | `{events: [{kind: down\|drag\|up\|move\|doubleclick\|tripleclick\|contextclick, x, y, mods?}], mods?}` | Drive the active tool in slide points through the same code path as the mouse (`contextclick`: the right-button press that selects before the context menu) |
| `ui.key` / `ui.text` | `{key, shift?, alt?, cmd?}` / `{text}` | Keyboard input (also drives a running slide show) |
| `ui.pointer` | `{events: [{kind: down\|drag\|up\|move\|doubleclick\|tripleclick, x, y, mods?}], mods?}` | Drive the active tool in slide points through the same code path as the mouse |
| `ui.key` / `ui.text` | `{key, shift?, alt?, cmd?}` / `{text}` | Keyboard input (also drives a running slide show: G is See All Slides, Z arms zoom, Esc closes those before it leaves the show) |
| `ui.key` / `ui.text` | `{key, shift?, alt?, cmd?}` / `{text}` | Keyboard input (also drives a running slide show: G is See All Slides, Z arms zoom, T pauses the presenter timer, R resets it, `[` and `]` change notes size, Esc closes the grid and zoom before it leaves the show) |
| `ui.ime` | `{preedit, active?: [start, end]}` / `{commit}` | An input method's composition through egui, as a real one arrives: shown in place on the slide or in a focused field; `{"preedit": ""}` cancels |
| `ui.move` / `ui.click` / `ui.drag` | screen points | Real egui pointer input — reaches every widget, menu and pane |
| `ui.set` | `{tab?, view?, pane?, notes?, zoom?, brightness?, ruler?, gridlines?, guides?, …}` | UI state (returns the full state) |
+4 -2
View File
@@ -11,6 +11,7 @@
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (six original starter decks; the large template gallery stays after beta; previous: minor, core-workflow alpha gate added; stage stays alpha) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** trivial (owned PPTX corpus covers more deck kinds; handout-master text kept; previous: minor, core-workflow alpha gate) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (show: See All Slides and zoom; previous: minor, core-workflow alpha gate) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (presenter timer pause/reset, clock and notes size; previous: trivial, See All Slides and zoom) · **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
@@ -27,7 +28,7 @@ points and lead the beta plan.
|---|---|---|---|
| Build a deck from scratch (theme, layouts, titles and bullets, shapes, pictures from file), save, close and reopen it | yes | Commands for all of it; `.deckcraft` and `.pptx` round-trip tests (`crates/pptx/tests/roundtrip.rs`, `crates/format`); AutoRecover. Close discards edits without asking (#60, PR #75); image *paste* fails on Windows/Linux (#53), though insert-from-file works | 1–3 |
| Open a colleague's `.pptx`, edit it, save it back as `.pptx` that PowerPoint opens | partial (not blocking) | The reader is lenient and keeps unknown objects. Files open in PowerPoint without repair (checked 2026-10-06). But only generated decks have been tested; text inside EMF/WMF pictures is missing; comment threads, media options and rewind are lost (#70, #74, #77) | 31–55 |
| Present: full-screen show with transitions, animations, media, presenter view with notes, next slide and timer | yes | 48/48 transitions, 64 effects, triggers, builds, media playback, presenter window, See All Slides and zoom. Presenter view isn't placed on the second display automatically (drag it there) | 3–5 |
| Present: full-screen show with transitions, animations, media, presenter view with notes, next slide and timer | yes | 48/48 transitions, 64 effects, triggers, builds, media playback, presenter window, See All Slides and zoom. The presenter timer pauses and resets, and notes have a size control. Presenter view isn't placed on the second display automatically (drag it there; #433) | 2–4 |
| Add business content: tables, charts with their data, simple diagrams | partial (not blocking) | Tables with styles, merge/split, all D or P. Charts insert with the common types and edit data in the Chart Data dialog (P0 partial); SmartArt is basic (pre-drawn shapes) | 12–20 |
| Share the deck: PDF of slides, notes or handouts; images | yes | `file.export` PDF with text layer, links, bookmarks, handouts 1–9; PNG/JPEG. Artwork is raster in the PDF; no direct print (print the PDF) | 10–16 |
| Review: comments with replies, resolve, spelling | partial (not blocking) | Comments, replies and resolve work and survive `.deckcraft` and `.pptx` (#70); spelling is a small built-in list | 4–8 |
@@ -76,7 +77,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 |
| 9 | Animation Pane timeline, preview, effect options | 5–8 | Features |
| 10 | Presenter view on the right display, timer controls | 3–5 | Hardware, Slide show |
| 10 | Presenter view on the right display, swap displays | 2–4 | Hardware, Slide show |
| 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% |
@@ -153,6 +154,7 @@ plan above.
| 2026-10-14 | trivial | Review workflow: previous and next comment across the deck |
| 2026-10-14 | minor | After beta: six original starter decks exist; a larger template gallery remains |
| 2026-10-13 | trivial | Owned PPTX corpus covers tables, charts, animations, media, sections and masters; handout-master text kept. Beta item 2 stays open for a larger corpus |
| 2026-10-14 | trivial | Present workflow notes presenter timer pause/reset, clock and notes size. Display placement stays open |
| 2026-10-14 | trivial | Present workflow notes See All Slides and zoom |
| 2026-10-10 | minor | Added the alpha gate (six core workflows): all pass, two with partial depth; stage stays alpha |
| 2026-10-10 | major | Created: current focus, ranked beta plan with hours, milestones M0–M14 with status, the old alpha checklist as history |
+8 -2
View File
@@ -6,6 +6,7 @@
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (laser pointer in the show; the presenter window still does not move it; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Show Media Controls hides the show's playback bar and GIF button; breadth stays 66%) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (See All Slides and zoom during the show; black and white from the presenter window; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (presenter timer pause/reset, clock and notes size; previous: minor, See All Slides and zoom) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
Presenting is the job PowerPoint is hired for. This doc covers the show window, presenter view,
rehearsal and recording. Transition and animation playback are in
@@ -51,12 +52,15 @@ ready** (estimated), **10–16 h** to parity.
|---|---|---|---|
| Separate presenter window with current slide, next slide, notes | yes | yes (an egui viewport, 1100×700) | |
| Elapsed timer | yes | yes | |
| Pause / reset timer, clock | yes | no | |
| Pause / reset timer, clock | yes | yes: Pause/Resume and Reset in presenter view (T pauses, R resets); the clock is local time, `--:--` where there is no clock | Pausing the timer does not pause the show, animations or rehearsal |
| Slide counter, previous/next/end buttons | yes | yes | |
| Keyboard navigation while Presenter View has focus | yes | yes | Shares the audience window's key handling; handled keys are consumed before buttons, and the presenter stays available at the end screen |
| Notes font size, scrolling | yes | scrolling only | |
| Placed on the presenter's display automatically, show on the projector | yes | **no**: opens wherever the OS puts it | Beta blocker, 3–5 h |
| Swap displays | yes | no | |
| Notes font size, scrolling | yes | yes: A- / A+ (and `[` / `]`), 12–48 pt in steps of 4, default 18; the notes scroll | |
| Placed on the presenter's display automatically, show on the projector | yes | **no**: opens wherever the OS puts it | Open PR #433 places the show on a second display when one is attached. This change does not |
| Swap displays | yes | no | Still open on #433 |
| Pen, laser, black screen, zoom from presenter view | yes | Black, White and Zoom buttons; the audience zoom is framed on the presenter slide | Pen and the laser pointer are still keys only; there is no laser yet |
| See all slides from presenter view | yes | yes: All slides opens the grid on the show | |
| Rehearse with Coach (Speaker Coach) | yes (cloud) | no | AI dimension |
@@ -81,9 +85,10 @@ Reading View in a window with navigation: present (checklist row done).
| Presenter view on the right display, swap displays, pause/reset timer, clock, notes size | 3–5 |
| Highlighter, eraser, keep-annotations prompt, laser from presenter view | 2–4 |
| See All Slides and zoom during the show | 2–3 |
| Presenter view on the right display and a swap-displays command | 2–4 |
| Laser pointer, highlighter, eraser, keep-annotations prompt | 2–4 |
| Record slide show with narration (no camera) | 5–8 |
| **Total** | **~10–17** (the slide-show row of the feature table carries 10–16 h; recording overlaps the Media row) |
| **Total** | **~9–16** (the slide-show row of the feature table carries 10–16 h; recording overlaps the Media row) |
## Revision history
@@ -95,5 +100,6 @@ Reading View in a window with navigation: present (checklist row done).
| 2026-10-11 | minor | Media effects (Play, Pause, Stop) in the click sequence play with their step (#116) |
| 2026-10-13 | minor | The show has a laser pointer (Cmd/Ctrl+L, the show's laser colour, no ink). Presenter view still does not drive it |
| 2026-10-14 | trivial | Show Media Controls hides the show's playback bar and GIF button |
| 2026-10-14 | minor | Presenter view pauses and resets its timer (T, R) and changes notes size (A-/A+, brackets). The clock shows local time. Second-display placement and swap stay with #433. Checklist breadth stays 66% |
| 2026-10-14 | minor | See All Slides (G) and click-to-zoom (Z) during the show. Presenter view opens that grid and sets black, white and zoom. Checklist breadth stays 66% |
| 2026-10-10 | major | Created: show, presenter view, rehearse and record, compared with PowerPoint 16.113's Slide Show tab and menu |
+5 -2
View File
@@ -6,6 +6,7 @@
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (228 commands; previous/next comment; breadth stays 79%; previous: minor, hours to ~95%) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** minor (six original starter decks; ecosystem stays ~25% until the gallery is large; previous: minor, hours to ~95% per audience added; ready 56% → 55%) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (slide show lists See All Slides and zoom; breadth stays 79%; previous: minor, hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (slide show lists presenter timer pause/reset, clock and notes size; breadth stays 79%; previous: trivial, See All Slides and zoom) · **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;
@@ -234,8 +235,9 @@ Carried from the 2026-10-07 ROADMAP status and updated with what has landed sinc
geometry outlines), 64 animation effects (entrance, emphasis, exit, motion paths, media),
triggers, by-paragraph builds, Animation Painter, on a shared timeline engine.
- **Slide show:** full screen, keyboard/mouse navigation, blank screens, go-to-slide, See All
Slides, click-to-zoom, pen, presenter window (black, white, zoom and the slide grid), rehearse
timings, custom shows, Set Up Show, Reading View; links and shape actions followed on click.
Slides, click-to-zoom, pen, presenter window (black, white, zoom, the slide grid, a timer that
pauses and resets, a local clock, and notes size), rehearse timings, custom shows, Set Up Show,
Reading View; links and shape actions followed on click.
- **Views:** Normal, Outline, Slide Sorter, Notes Page, Reading View, Slide Master; zoom;
grayscale.
- **Review:** comments with replies and resolve, previous and next across the deck, accessibility checker, a basic spelling list.
@@ -288,6 +290,7 @@ scored done (16 open issues).
| 2026-10-14 | trivial | Size and Position dialog. Command count 226 → 227. Breadth stays 79%, ready stays ~55%, UI stays ~60% |
| 2026-10-14 | trivial | 228 commands; Review lists previous and next comment. Breadth stays 79%; the comments checklist row stays D |
| 2026-10-14 | minor | Ecosystem evidence: six original starter decks. The dimension stays ~25% because PowerPoint's large gallery is still absent |
| 2026-10-14 | trivial | Slide show evidence includes presenter timer pause/reset, a local clock and notes size. Breadth stays 79% |
| 2026-10-14 | trivial | Slide show evidence includes See All Slides and zoom. Breadth stays 79% |
| 2026-10-10 | minor | Added the readiness-by-audience table: each number gets its own hours to ~95% (full 340–600, mainstream 150–250, essentials 50–90). The full number stays an additive weighted sum; no change to the percentages |
| 2026-10-10 | minor | Added the mainstream practitioner (~53%) and essentials user (~62%) numbers with written weights, discounts and user evidence. Features dimension recomputed from written area weights (66% judgement → 62.4% weighted mean), which moves ready for real work from ~56% to ~55%; stage unchanged |