From d31ed52bbe12f84cd3029bea3815ed6808d70ca0 Mon Sep 17 00:00:00 2001 From: Nikita Rakitin Date: Wed, 14 Oct 2026 04:04:09 +0300 Subject: [PATCH] Show: presenter view keeps a portrait Next slide inside its column The Next preview took the column's full width and the slide's aspect for its height, so a portrait slide (up to 1:56) drew a white slab many times the window's height over the notes, which were pushed below the window. It is now fitted into the top 60% of the column; 16:9 slides keep the full width as before. Co-authored-by: Cursor --- crates/ui-egui/src/show.rs | 30 ++++++++++++++++++++--- crates/ui-egui/src/texture_limit_tests.rs | 16 ++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/ui-egui/src/show.rs b/crates/ui-egui/src/show.rs index a0dfcc30..01188544 100644 --- a/crates/ui-egui/src/show.rs +++ b/crates/ui-egui/src/show.rs @@ -208,6 +208,14 @@ fn fit(avail: Rect, doc: &Presentation) -> Rect { /// A transition layer as an egui mesh: [`deckcraft_anim::layer_mesh`]'s triangles (perspective /// cards subdivided so their pictures stay straight) with per-vertex opacity. +/// Where presenter view's "Next" preview goes: the column's width below its label, kept inside +/// the top 60% of the column. A portrait slide used to get the full width and a height many +/// times the window (a white slab over the notes and past the window edge). +fn next_rect(right: Rect, doc: &Presentation) -> Rect { + let area = Rect::from_min_max(right.min + vec2(0.0, 22.0), pos2(right.max.x, right.min.y + 22.0 + (right.height() - 22.0).max(0.0) * 0.6)); + Rect::from_min_size(area.min, fit(area, doc).size()) +} + fn quad_mesh(tex: Option, rect: Rect, l: &Layer, color: Color32) -> Mesh { let mut m = match tex { Some(id) => Mesh::with_texture(id), @@ -835,10 +843,7 @@ fn presenter_window(ctx: &egui::Context, show: &mut Show, doc: &Presentation, no if let Some(t) = &cur_tex { ui.painter().image(t.id(), cr, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE); } - let nr = Rect::from_min_size( - right.min + vec2(0.0, 22.0), - vec2(right.width(), right.width() * (doc.slide_size.height / doc.slide_size.width.max(1.0)) as f32), - ); + let nr = next_rect(right, doc); ui.painter().text(right.min, Align2::LEFT_TOP, "Next", theme::font(13.0), Color32::from_gray(170)); match &next_tex { Some(t) => { @@ -891,4 +896,21 @@ mod tests { assert_eq!(tr.from, Some(0)); assert!(show.tex.is_none(), "the new slide gets a texture of its own instead of overwriting the old one"); } + + #[test] + fn the_next_preview_fits_its_column_whatever_the_slide_shape() { + let right = Rect::from_min_size(pos2(700.0, 100.0), vec2(400.0, 600.0)); + let mut s = deckcraft_engine::Session::with_new(); + for (w, h) in [(72, 4032), (4032, 72), (960, 540)] { + s.execute("design.slideSize", &json!({"w": w, "h": h})).unwrap(); + let doc = s.active().unwrap().doc.clone(); + let r = next_rect(right, &doc); + assert!(right.contains_rect(r), "{w}x{h}: {r:?}"); + let aspect = r.height() / r.width(); + assert!((aspect - h as f32 / w as f32).abs() / (h as f32 / w as f32) < 0.01, "{w}x{h}: {r:?}"); + } + // 16:9 still takes the column's full width. + let doc = s.active().unwrap().doc.clone(); + assert_eq!(next_rect(right, &doc).width(), 400.0); + } } diff --git a/crates/ui-egui/src/texture_limit_tests.rs b/crates/ui-egui/src/texture_limit_tests.rs index bf5c648d..69dbf523 100644 --- a/crates/ui-egui/src/texture_limit_tests.rs +++ b/crates/ui-egui/src/texture_limit_tests.rs @@ -51,3 +51,19 @@ fn a_very_tall_slide_in_the_editor_views_and_presenter_view() { h.run_steps(4); assert!(h.state().show.is_some()); } + +/// Presenter view of a portrait slide: the "Next" preview keeps the slide's shape inside its +/// column. It took the column's full width and 56 times that in height, a white slab over the +/// notes and past the window. +#[test] +fn presenter_view_keeps_a_portrait_next_slide_in_its_column() { + let mut h = harness(tall_deck(), egui::vec2(1280.0, 800.0)); + h.run_steps(3); + h.state_mut().run("show.presenter", json!({"on": true})).unwrap(); + h.state_mut().run("show.start", json!({"from": 0})).unwrap(); + h.run_steps(4); + let img = h.render().unwrap(); + // Right of the Next column's narrow preview, below where a landscape one would end. + let px = img.get_pixel(1000, 600).0; + assert!(px[0] < 128 && px[1] < 128 && px[2] < 128, "the Next preview spills over the column: {px:?}"); +}