mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-31 05:56:55 -06:00
Merge pull request #1456
Slide show: Set Up Slide Show can keep the mouse cursor visible (#1445) # Conflicts: # crates/ui-egui/src/app/commands.rs # crates/ui-egui/src/lib.rs
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~20 points and ~120–210 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-27 · **Change:** trivial (progress log: Set Up Slide Show ▸ Keep mouse cursor visible, #1445; stage and scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; 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-24 · **Change:** trivial (progress log: Ctrl+wheel zoom during the slide show, #1286; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; 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-24 · **Change:** trivial (progress log: picture and embedded object fills; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
@@ -138,6 +139,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-29 | Slide Show: going back to the previous slide plays the transition of the slide being left, reversed, as PowerPoint does, instead of the previous slide's own entrance transition (#1531); a jump (Go to Slide, links) still plays the target slide's transition |
|
||||
| 2026-10-27 | Set Up Slide Show ▸ Keep mouse cursor visible: a full-screen show no longer hides a still pointer, so the choices of a branching deck stay visible and clickable; the setting is kept with the user's preferences (`ui.json`, `showKeepCursor`), off by default (#1445) |
|
||||
| 2026-10-25 | Windows installer: the Start menu shortcut takes the icon embedded in `deckcraft.exe` instead of an MSI `<Icon>`, so a taskbar pin keeps its icon after an update (#1114); a pin that already lost it has to be unpinned and pinned again once |
|
||||
| 2026-10-24 | AutoFormat as you type: a paragraph that starts with `*`, `-`, `--`, `>`, `<>`, `1.`, `1)`, `I.`, `i.`, `A.`, `a.` or `a)` and a space or tab becomes a bulleted or numbered list in that style; Backspace right after puts the typed text back, and Tools ▸ AutoCorrect and `text.autocorrect {lists}` turn it off |
|
||||
| 2026-10-24 | Tables: while the caret is in a cell, dragging into another cell selects that range of cells (shaded, merged cells taken in whole), and Shift+arrow grows or shrinks it; formatting applies to the selected cells only, and Delete clears their text and keeps the table |
|
||||
|
||||
@@ -169,6 +169,12 @@ impl SlideApp {
|
||||
// The browser build has one viewport: presenter view is drawn inside the page.
|
||||
return Ok(json!({"on": self.ui.presenter_view, "window": !cfg!(target_arch = "wasm32")}));
|
||||
}
|
||||
"show.keepCursor" => {
|
||||
self.ui.show_keep_cursor = self.flag(p, self.ui.show_keep_cursor);
|
||||
if let Some(s) = self.show.as_mut() {
|
||||
s.keep_cursor = self.ui.show_keep_cursor;
|
||||
}
|
||||
}
|
||||
"show.end" => {
|
||||
self.show = None;
|
||||
self.media.stop_owned(media::Owner::Show);
|
||||
|
||||
@@ -36,6 +36,7 @@ impl SlideApp {
|
||||
self.show_instance = self.show_instance.wrapping_add(1).max(1);
|
||||
let mut show = show::Show::new(&d.doc, from, reading, self.ui.presenter_view && !reading);
|
||||
show.instance = self.show_instance;
|
||||
show.keep_cursor = self.ui.show_keep_cursor;
|
||||
self.show = Some(show);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ fn dlg_setup_show(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
let nn = check(ui, d, "Show without narration", "nn", sh.without_narration);
|
||||
let na = check(ui, d, "Show without animation", "na", sh.without_animation);
|
||||
let ut = check(ui, d, "Use timings, if present", "ut", sh.use_timings);
|
||||
let kc = check(ui, d, "Keep mouse cursor visible", "kc", app.ui.show_keep_cursor);
|
||||
let names: Vec<String> = app.session.active().map(|s| s.doc.custom_shows.iter().map(|c| c.name.clone()).collect()).unwrap_or_default();
|
||||
let chosen = d.fields.entry("custom".into()).or_insert_with(|| sh.custom_show.clone().unwrap_or_default()).clone();
|
||||
let field = ui.label("Slides");
|
||||
@@ -279,6 +280,7 @@ fn dlg_setup_show(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
let ty = d.get("type", "speaker");
|
||||
let custom = d.fields.get("custom").cloned().unwrap_or_default();
|
||||
run(app, "show.setup", json!({"type": ty, "loop": lp, "noNarration": nn, "noAnimation": na, "useTimings": ut, "custom": custom}));
|
||||
run(app, "show.keepCursor", json!({"on": kc}));
|
||||
}
|
||||
ok || cancel
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
||||
None,
|
||||
"{on?: bool} → {on, window} (presenter view, with notes, a clock and the next slide, appears in its own separate window beside the show window when the show starts; window is false in the browser build, which draws it inside the page)",
|
||||
),
|
||||
("show.keepCursor", "Keep Mouse Cursor Visible", None, "{on?: bool}"),
|
||||
("show.end", "End Show", None, "{}"),
|
||||
("animation.preview", "Preview Animations", None, "{on?: bool, from?: effect index}"),
|
||||
("app.home", "Home", None, "{on?}"),
|
||||
|
||||
@@ -46,6 +46,8 @@ pub struct Show {
|
||||
/// Play every step automatically (animation preview).
|
||||
pub autoplay: bool,
|
||||
pub rehearse: bool,
|
||||
/// Set Up Slide Show ▸ Keep mouse cursor visible: a still pointer is not hidden (#1445).
|
||||
pub keep_cursor: bool,
|
||||
timeline: Option<(usize, Timeline)>,
|
||||
step_start: f64,
|
||||
slide_start: f64,
|
||||
@@ -211,6 +213,7 @@ impl Show {
|
||||
preview_only: false,
|
||||
autoplay: false,
|
||||
rehearse: false,
|
||||
keep_cursor: false,
|
||||
timeline: None,
|
||||
step_start: 0.0,
|
||||
slide_start: 0.0,
|
||||
@@ -367,6 +370,12 @@ impl Show {
|
||||
now - self.last_move < 2.5
|
||||
}
|
||||
|
||||
/// Whether a still pointer is hidden: after 2.5 s in a full-screen show, unless Keep mouse
|
||||
/// cursor visible is on. Reading View always keeps it.
|
||||
fn hides_idle_cursor(&self, now: f64) -> bool {
|
||||
!self.pointer_awake(now) && !self.reading && !self.keep_cursor
|
||||
}
|
||||
|
||||
/// Pen and laser are different pointers: turning one on puts the other away.
|
||||
fn set_pen(&mut self, on: bool) {
|
||||
self.pen = on;
|
||||
|
||||
@@ -52,6 +52,79 @@ fn a_still_cursor_hides_over_a_clickable_shape() {
|
||||
assert_eq!(harness.output().platform_output.cursor_icon, egui::CursorIcon::None, "still for 3 s: hidden");
|
||||
}
|
||||
|
||||
/// #1445: Keep mouse cursor visible leaves a still pointer showing over a link, so a branching
|
||||
/// deck's choices stay targetable; the setting outlives the show and the link still follows.
|
||||
#[test]
|
||||
fn keep_cursor_visible_leaves_a_still_cursor_over_a_link() {
|
||||
let mut session = deckcraft_engine::Session::with_new();
|
||||
let size = session.active().unwrap().doc.slide_size;
|
||||
session.execute("slide.new", &json!({})).unwrap();
|
||||
session.execute("slide.go", &json!({"index": 0})).unwrap();
|
||||
let r = session.execute("shape.insert", &json!({"preset": "rect", "rect": [0.0, 0.0, size.width, size.height]})).unwrap();
|
||||
session.execute("insert.hyperlink", &json!({"slide": 1, "ids": [r["id"]]})).unwrap();
|
||||
let mut app = SlideApp::new(session, crate::Services::default());
|
||||
app.ui.presenter_view = false;
|
||||
app.run("show.keepCursor", json!({"on": true})).unwrap();
|
||||
let mut harness = egui_kittest::Harness::builder().with_size(vec2(1000.0, 700.0)).with_step_dt(0.25).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
harness.run_steps(3);
|
||||
harness.state_mut().start_show(0, false);
|
||||
harness.run_steps(2);
|
||||
harness.hover_at(pos2(500.0, 350.0));
|
||||
harness.run_steps(14);
|
||||
assert_eq!(harness.output().platform_output.cursor_icon, egui::CursorIcon::PointingHand, "still for 3 s: kept");
|
||||
click_at(&mut harness, pos2(500.0, 350.0));
|
||||
harness.run_steps(2);
|
||||
assert_eq!(harness.state().show.as_ref().map(|s| s.state.slide), Some(1), "the link still follows");
|
||||
assert!(harness.state().ui.show_keep_cursor, "the setting is kept for the next show");
|
||||
}
|
||||
|
||||
/// #1445: Set Up Slide Show turns Keep mouse cursor visible on and off.
|
||||
#[test]
|
||||
fn set_up_slide_show_keeps_the_cursor_visible() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
||||
let app = SlideApp::new(deckcraft_engine::Session::with_new(), crate::Services::default());
|
||||
let mut harness = egui_kittest::Harness::builder().with_size(vec2(1200.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
for on in [true, false] {
|
||||
harness.state_mut().run("app.dialog", json!({"id": "setupShow"})).unwrap();
|
||||
harness.run_steps(6);
|
||||
harness.get_by_label("Keep mouse cursor visible").click();
|
||||
harness.run_steps(3);
|
||||
harness.get_by_label("OK").click();
|
||||
harness.run_steps(2);
|
||||
assert!(harness.state().dialog.is_none());
|
||||
assert_eq!(harness.state().ui.show_keep_cursor, on);
|
||||
}
|
||||
}
|
||||
|
||||
/// #1445: the idle hide follows the pointer's last move, Reading View and Keep mouse cursor visible.
|
||||
#[test]
|
||||
fn the_idle_cursor_hides_only_when_asked() {
|
||||
let session = deckcraft_engine::Session::with_new();
|
||||
let doc = session.active().unwrap().doc.clone();
|
||||
let mut show = Show::new(&doc, 0, false, false);
|
||||
show.last_move = 10.0;
|
||||
assert!(!show.hides_idle_cursor(12.0), "moved 2 s ago");
|
||||
assert!(show.hides_idle_cursor(13.0), "still for 3 s");
|
||||
show.keep_cursor = true;
|
||||
assert!(!show.hides_idle_cursor(13.0), "kept visible");
|
||||
show.keep_cursor = false;
|
||||
show.reading = true;
|
||||
assert!(!show.hides_idle_cursor(13.0), "Reading View never hides it");
|
||||
}
|
||||
|
||||
/// #1130: the Presenter View's All slides button opens the grid in that window, not on the audience screen.
|
||||
#[test]
|
||||
fn presenter_all_slides_does_not_cover_the_audience_screen() {
|
||||
|
||||
@@ -605,7 +605,7 @@ fn draw_pen_and_hints(ui: &mut Ui, show: &mut Show, f: &Fr, painter: &egui::Pain
|
||||
ctx.set_cursor_icon(egui::CursorIcon::None);
|
||||
} else if show.zoom_arm || show.grid {
|
||||
ctx.set_cursor_icon(egui::CursorIcon::Crosshair);
|
||||
} else if !show.pointer_awake(now) && !show.reading {
|
||||
} else if show.hides_idle_cursor(now) {
|
||||
// Idle first: a still pointer hides also over a clickable shape, such as a playing clip.
|
||||
ctx.set_cursor_icon(egui::CursorIcon::None);
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(500));
|
||||
|
||||
@@ -76,6 +76,9 @@ pub struct UiState {
|
||||
pub sorter_zoom: f32,
|
||||
pub recent: Vec<String>,
|
||||
pub presenter_view: bool,
|
||||
/// Set Up Slide Show ▸ Keep mouse cursor visible: full-screen shows never hide a still pointer,
|
||||
/// for decks with clickable choices (#1445).
|
||||
pub show_keep_cursor: bool,
|
||||
pub ui_scale: f32,
|
||||
/// Format Shape pane: tab (`fill`, `effects`, `size`, `picture`, `text`).
|
||||
pub format_tab: String,
|
||||
@@ -115,6 +118,7 @@ impl Default for UiState {
|
||||
sorter_zoom: 1.0,
|
||||
recent: vec![],
|
||||
presenter_view: true,
|
||||
show_keep_cursor: false,
|
||||
ui_scale: 1.0,
|
||||
format_tab: "fill".into(),
|
||||
smartart_row: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Slide show and presenter view: parity with PowerPoint
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** minor (Ctrl+wheel zooms the show around the pointer, #1286; previous: major, community PR wave 2026-10-14: interactive decks, laser pointer, See All Slides and zoom, go-to-slide number, custom show playback, presenter timer, clock, notes size and keyboard, Show Media Controls, swipe; breadth 68%; duplicated rows and headers folded; previous: major, created) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-27 · **Change:** minor (Set Up Slide Show ▸ Keep mouse cursor visible, #1445; previous: minor, Ctrl+wheel zooms the show around the pointer, #1286; previous: major, community PR wave 2026-10-14: interactive decks, laser pointer, See All Slides and zoom, go-to-slide number, custom show playback, presenter timer, clock, notes size and keyboard, Show Media Controls, swipe; breadth 68%; duplicated rows and headers folded; previous: major, created) · **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
|
||||
@@ -34,7 +34,7 @@ ready** (estimated), **10–16 h** to parity.
|
||||
| Laser pointer | yes | yes in the show | Cmd/Ctrl+L and the on-screen button draw the show's laser colour and leave no ink. The presenter window does not move that dot |
|
||||
| Subtitles and live captions | yes (cloud speech) | no | AI dimension |
|
||||
| Custom shows | yes | yes: Show plays that list in order and does not change Set Up; Set Up Slide Show can keep one for Play from Start | Hidden slides in the list are skipped |
|
||||
| Set Up Show: loop until Esc, kiosk (browsed at a kiosk), show without animation/narration, pen colour, slides range | yes | yes, most options | |
|
||||
| Set Up Show: loop until Esc, kiosk (browsed at a kiosk), show without animation/narration, pen colour, slides range | yes | yes, most options | Keep mouse cursor visible (DeckCraft's own, #1445) stops the show hiding a still pointer after 2.5 s, for decks with clickable choices; it is a user preference (`ui.json`, `showKeepCursor`), since PPTX has no place for it |
|
||||
| Use rehearsed timings | yes | yes | |
|
||||
|
||||
## Presenter view
|
||||
@@ -79,6 +79,7 @@ Reading View in a window with navigation: present (checklist row done).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-27 | minor | Set Up Slide Show ▸ Keep mouse cursor visible: full-screen shows keep a still pointer, so clickable choices stay targetable; Reading View unchanged (#1445) |
|
||||
| 2026-10-22 | minor | See All Slides from presenter view opens in presenter view, not on the audience screen (#1130) |
|
||||
| 2026-10-14 | trivial | Community PR wave folded: one media playback row, the presenter pen/laser row matches the laser that exists, estimates keep only the open work (~9–16 h); checklist breadth 68% |
|
||||
| 2026-10-13 | minor | Click handling for interactive decks: triggers, per-slide state kept across visits, visible-shape hit testing with hand cursor, advance on click |
|
||||
|
||||
Reference in New Issue
Block a user