mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
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:
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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 { ¬es }).size(18.0).color(Color32::from_gray(230)));
|
||||
ui.label(
|
||||
egui::RichText::new(if notes.is_empty() { "No notes." } else { ¬es }).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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user