From 884e3d3353649870127280eb8bf0bd9c40c5cf48 Mon Sep 17 00:00:00 2001 From: FredApps <153173854+FredApps@users.noreply.github.com> Date: Thu, 5 Nov 2026 02:37:37 +0100 Subject: [PATCH] Show: keep web All slides messages small, clear the grid when the popup closes, show it on the end screen (#1866) --- crates/ui-egui/src/presenter.rs | 6 +++--- crates/ui-egui/src/show.rs | 1 + crates/ui-egui/src/show/tests.rs | 13 ++++++++++++- crates/ui-egui/src/show/ui.rs | 23 +++++++++++++++-------- crates/ui-egui/src/show/zoom.rs | 2 +- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/crates/ui-egui/src/presenter.rs b/crates/ui-egui/src/presenter.rs index 0ad7929bc..5d99e63c8 100644 --- a/crates/ui-egui/src/presenter.rs +++ b/crates/ui-egui/src/presenter.rs @@ -228,7 +228,7 @@ impl View { } }); ui.separator(); - if snapshot.grid && !snapshot.ended { + if snapshot.grid { // All slides opens here, as in the desktop Presenter View; the audience keeps the current slide. self.grid_ui(ui, snapshot, &mut commands); return commands; @@ -303,8 +303,8 @@ impl View { } ui.heading("All slides"); ui.label("Choose a slide to show it, or press G to return."); - if snapshot.total > slides.len() && !slides.is_empty() { - ui.label(format!("First {} slides", slides.len())); + if snapshot.total > crate::show::GRID_CAP { + ui.label(format!("First {} slides", crate::show::GRID_CAP)); } egui::ScrollArea::vertical().show(ui, |ui| { ui.horizontal_wrapped(|ui| { diff --git a/crates/ui-egui/src/show.rs b/crates/ui-egui/src/show.rs index c1e3f4ab2..db7983c2b 100644 --- a/crates/ui-egui/src/show.rs +++ b/crates/ui-egui/src/show.rs @@ -18,6 +18,7 @@ mod presenter; mod render; mod ui; mod zoom; +pub(crate) use zoom::GRID_CAP; #[cfg(test)] use media::{on_screen, pointer_on_media_bar, shown_slide_rect, slide_media_bars}; diff --git a/crates/ui-egui/src/show/tests.rs b/crates/ui-egui/src/show/tests.rs index 01b30ff01..a01989ac0 100644 --- a/crates/ui-egui/src/show/tests.rs +++ b/crates/ui-egui/src/show/tests.rs @@ -1133,6 +1133,7 @@ fn remote_presenter_uses_the_audience_state_and_closes_with_the_show() { closes: usize, commands: Vec, last: Option, + gone: bool, } struct Mock(Rc>); impl Host for Mock { @@ -1141,7 +1142,7 @@ fn remote_presenter_uses_the_audience_state_and_closes_with_the_show() { Ok(()) } fn is_open(&self) -> bool { - true + !self.0.borrow().gone } fn commands(&mut self) -> Vec { std::mem::take(&mut self.0.borrow_mut().commands) @@ -1194,11 +1195,21 @@ fn remote_presenter_uses_the_audience_state_and_closes_with_the_show() { let last = bridge.borrow().last.clone().unwrap(); assert!(last.grid); assert_eq!(last.slides.as_ref().map(Vec::len), Some(3), "the popup gets a thumbnail per slide"); + assert!(last.slides.iter().flatten().all(|p| p.width.max(p.height) <= 96), "thumbnails stay small enough for one message"); + assert!(last.current.is_none() && last.next.is_none(), "the grid replaces both previews"); bridge.borrow_mut().commands.push(Command::Goto { slide: 2 }); h.run_steps(2); let show = h.state().show.as_ref().unwrap(); assert!(!show.presenter_grid && !show.grid); assert_eq!(show.state.slide, 2); + // Closing the popup with its grid open must not hold auto-advance for the rest of the show. + bridge.borrow_mut().commands.push(Command::Key { name: "G".into(), command: false, shift: false, alt: false, ctrl: false }); + h.run_steps(2); + assert!(h.state().show.as_ref().unwrap().presenter_grid); + bridge.borrow_mut().gone = true; + h.run_steps(2); + assert!(!h.state().show.as_ref().unwrap().presenter_grid); + bridge.borrow_mut().gone = false; bridge.borrow_mut().commands.push(Command::End); h.run_steps(2); assert!(h.state().show.is_none()); diff --git a/crates/ui-egui/src/show/ui.rs b/crates/ui-egui/src/show/ui.rs index 688d2bb6e..d048fb93c 100644 --- a/crates/ui-egui/src/show/ui.rs +++ b/crates/ui-egui/src/show/ui.rs @@ -80,6 +80,10 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) { && !show.reading && let Some(host) = app.services.presenter.as_mut() { + if show.remote_opened && show.presenter_grid && !host.is_open() { + // A grid left open in the closed popup would hold auto-advance for the rest of the show. + show.presenter_grid = false; + } if !show.remote_opened { show.remote_opened = true; if let Err(message) = host.open() { @@ -157,12 +161,11 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) { } else if show.remote_grid.len() < grid_indices(doc.slides.len()).len() { // A few thumbnails per update keep the audience window responsive on the web. for i in grid_indices(doc.slides.len()).into_iter().skip(show.remote_grid.len()).take(4) { - let size = (160, (160.0 * doc.slide_size.height / doc.slide_size.width.max(1.0)).clamp(1.0, 640.0) as u32); - let image = deckcraft_render::render_slide( - &doc, - i, - &RenderOpts { scale: size.0 as f64 / doc.slide_size.width.max(1.0), size: Some(size), ..Default::default() }, - ); + // At most 96 px on the long side: 48 thumbnails stay well under the popup's message limit. + let (w, h) = (doc.slide_size.width.max(1.0), doc.slide_size.height.max(1.0)); + let scale = REMOTE_THUMB / w.max(h); + let size = (((w * scale).round() as u32).max(1), ((h * scale).round() as u32).max(1)); + let image = deckcraft_render::render_slide(&doc, i, &RenderOpts { scale, size: Some(size), ..Default::default() }); let preview = crate::presenter::Preview::from_image(&crate::textures::to_color_image(&image, false)); show.remote_grid.push(preview.unwrap_or(crate::presenter::Preview { width: 1, height: 1, rgba: vec![0; 4] })); } @@ -178,8 +181,9 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) { blank: show.blank_name().map(str::to_owned), grid: show.presenter_grid, zoom_armed: show.zoom_arm, - current: show.remote_preview.clone(), - next: show.remote_next.as_ref().filter(|(n, _)| Some(*n) == next).map(|(_, image)| image.clone()), + // The grid replaces both previews in the popup; leaving them out keeps one message small. + current: show.remote_preview.clone().filter(|_| !show.presenter_grid), + next: show.remote_next.as_ref().filter(|(n, _)| Some(*n) == next && !show.presenter_grid).map(|(_, image)| image.clone()), slides: show.presenter_grid.then(|| show.remote_grid.clone()), }); show.remote_sent = now; @@ -189,6 +193,9 @@ pub fn ui(app: &mut SlideApp, ui: &mut Ui) { finish(app, show, &f, out.keep); } +/// Longest side of an All slides thumbnail sent to the web presenter window (#1866). +const REMOTE_THUMB: f64 = 96.0; + fn paint_hidden(ui: &Ui, doc: &Presentation, show: &Show) { if show.started { let full = ui.max_rect(); diff --git a/crates/ui-egui/src/show/zoom.rs b/crates/ui-egui/src/show/zoom.rs index 9c45fd4a2..da6104905 100644 --- a/crates/ui-egui/src/show/zoom.rs +++ b/crates/ui-egui/src/show/zoom.rs @@ -182,7 +182,7 @@ const WHEEL_NOTCHES_PER_DOUBLING: f32 = 4.0; /// PowerPoint's slide show magnifier goes up to 400%. const WHEEL_ZOOM_MAX: f32 = 4.0; -pub(super) const GRID_CAP: usize = 48; +pub(crate) const GRID_CAP: usize = 48; const TOOL_BAR_W: f32 = 520.0;