From c32d776a0e3ce0d82aadce1647f8015fbf3a6073 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Sat, 24 Oct 2026 15:48:23 -0400 Subject: [PATCH] LRC-SS-LAYOUT: the Slideshow settings page no longer moves when the slide preview finishes rendering The preview's hint line was drawn only after the render had landed, so the pinned preview block grew by a line on the frame the render arrived (a worker thread, so any frame) and the page below it moved down. A click made in that frame hit the wrong place: the gate's tests_slideshow::playback_settings_drive_the_slideshow_and_bad_ones_are_refused clicked "Random order" and the checkbox never changed. The hint is drawn while the preview is still rendering too. A new test holds the render back, then lets it land, and checks that the page and a control keep their places. --- .../src/dialogs/settings/slide_layout.rs | 7 +++++- crates/ui-egui/src/tests/slideshow.rs | 23 +++++++++++++++++++ docs/whats-new.md | 5 ++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/dialogs/settings/slide_layout.rs b/crates/ui-egui/src/dialogs/settings/slide_layout.rs index 2fc55abda..c78d3df4d 100644 --- a/crates/ui-egui/src/dialogs/settings/slide_layout.rs +++ b/crates/ui-egui/src/dialogs/settings/slide_layout.rs @@ -247,9 +247,14 @@ pub fn preview(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens, max_h: f3 } else { ui.ctx().request_repaint_after(std::time::Duration::from_secs_f64((DEBOUNCE - still).clamp(0.01, DEBOUNCE))); } + let say = + |ui: &mut egui::Ui| hint(ui, t, "The selected photo as a slide. Playback fills the screen; the aspect preview only changes this preview."); let Some(tex) = app.renderer.textures.get(&Slot::SlidePreview).filter(|tex| tex.photo == id) else { p.rect_filled(rect, 0.0, Color32::from_rgb(s.backdrop_color[0], s.backdrop_color[1], s.backdrop_color[2])); p.text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Rendering…"), t.font(11.5), Color32::from_gray(200)); + // (the hint too: the render lands on a frame nobody can name, and a line that came with it would push + // the pinned preview's block, and the page under it, down while a control is being clicked) + say(ui); return; }; let drawn = crate::panels::detail::fit_texture_rect(rect, tex.size); @@ -269,7 +274,7 @@ pub fn preview(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens, max_h: f3 p.line_segment([pos2(drawn.left(), gy), pos2(drawn.right(), gy)], stroke); } } - hint(ui, t, "The selected photo as a slide. Playback fills the screen; the aspect preview only changes this preview."); + say(ui); } /// The preview showing title screen `title` (composed off the UI thread like the photo's slide, diff --git a/crates/ui-egui/src/tests/slideshow.rs b/crates/ui-egui/src/tests/slideshow.rs index 26c72c2cd..137dd9317 100644 --- a/crates/ui-egui/src/tests/slideshow.rs +++ b/crates/ui-egui/src/tests/slideshow.rs @@ -400,6 +400,29 @@ fn playback_settings_drive_the_slideshow_and_bad_ones_are_refused() { h.settle(SETTLE); } +/// The slide preview's render lands on a frame nobody can name (a worker thread); the page under it must +/// not move when it does, or a click made in that frame lands beside the control aimed at (the gate went +/// red on it: `playback_settings_…` clicks "Random order" and the checkbox never changed). +#[test] +fn the_settings_page_does_not_move_when_the_slide_preview_finishes_rendering() { + let mut h = grid(); + let open = std::rc::Rc::new(std::cell::Cell::new(false)); + h.app.renderer.set_offload(Box::new(crate::headless::RenderGate::new(open.clone()))); + run(&mut h, "view.slideshowOptions", json!({})); + // (the held render keeps the app "busy", so settle() would wait out its timeout: a second of frames instead) + for _ in 0..60 { + h.step(); + } + let rect = |h: &Headless, w: &str| h.app.widgets.iter().find(|(id, _)| id == w).map(|(_, r)| *r).unwrap_or_else(|| panic!("{w} is on screen")); + let (room, check) = (rect(&h, "settings:pageRoom"), rect(&h, "check:settings.slideshowRandom")); + assert!(!h.app.renderer.textures.contains_key(&Slot::SlidePreview), "the render is held back"); + open.set(true); + assert!(h.step_until(SETTLE, |h| h.app.renderer.textures.contains_key(&Slot::SlidePreview)), "the render lands"); + h.settle(Duration::from_secs(5)); + assert_eq!(rect(&h, "settings:pageRoom"), room, "the page's room is the same before and after the render"); + assert_eq!(rect(&h, "check:settings.slideshowRandom"), check, "a control stays where it was drawn"); +} + #[test] fn a_click_on_a_control_scrolled_out_of_the_settings_page_is_refused() { // A compact window leaves the lower controls of the Slideshow settings page outside the diff --git a/docs/whats-new.md b/docs/whats-new.md index c6c259200..f729f0aab 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -2,6 +2,11 @@ ## 2026-10-24 +### The Slideshow settings page stays put while the preview renders +- In Settings ▸ Slideshow, the line under the slide preview ("The selected photo as a slide…") now shows from the first + frame instead of only once the preview has finished rendering. Before, it appeared with the render, pushing the page + below it down by a line, so a click made in that moment could land beside the control you aimed at. + ### Relative-date filters and smart albums follow the clock - A current view or source count using “in the last” rules now refreshes when the session clock moves into a new minute, even when no catalog edit occurred. This fixes stale photos remaining in the grid and stale counts for