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 <cursoragent@cursor.com>
This commit is contained in:
Nikita Rakitin
2026-10-14 04:04:09 +03:00
co-authored by Cursor
parent 05eb4c611b
commit d31ed52bbe
2 changed files with 42 additions and 4 deletions
+26 -4
View File
@@ -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<egui::TextureId>, 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);
}
}
+16
View File
@@ -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:?}");
}