diff --git a/crates/ui-egui/src/dialogs/para_style_options.rs b/crates/ui-egui/src/dialogs/para_style_options.rs index c343e0682c..a78790d692 100644 --- a/crates/ui-egui/src/dialogs/para_style_options.rs +++ b/crates/ui-egui/src/dialogs/para_style_options.rs @@ -979,7 +979,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { ui.separator(); let section = d.str("section"); let content_w = ui.available_width(); - egui::ScrollArea::both() + let shown = egui::ScrollArea::both() .id_salt(("para-style-sections", section)) .max_height(room) .min_scrolled_height(room) @@ -987,15 +987,17 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { .auto_shrink([false, true]) .show(ui, |ui| { ui.vertical(|ui| section_body(ui, d)); - }) - .inner_rect - .height() + }); + // `inner_rect` is the height offered before a short section shrinks the area to its content. + shown.inner_rect.height().min(shown.content_size.y) }); let content_h = drawn.inner; let was = ui.ctx().data(|m| m.get_temp::(content_id(ui))); ui.ctx().data_mut(|m| m.insert_temp(content_id(ui), content_h)); + // A new height lays the frame out again before it is shown, so the window is placed at its new + // size and OK and Cancel stay on the screen in the frame of a section switch. if was.is_none_or(|was| (was - content_h).abs() > 0.5) { - ui.ctx().request_repaint(); + ui.ctx().request_discard("paragraph style content height changed"); } reset_button(ui, d); match edited_attrs(d) { diff --git a/crates/ui-egui/src/dialogs/para_style_options/tests.rs b/crates/ui-egui/src/dialogs/para_style_options/tests.rs index 0890491606..f3528b9ce6 100644 --- a/crates/ui-egui/src/dialogs/para_style_options/tests.rs +++ b/crates/ui-egui/src/dialogs/para_style_options/tests.rs @@ -578,3 +578,62 @@ fn every_section_draws_its_fields() { } } } + +/// The texts of one headless frame at a screen `w` by `h`, with their screen rects. +fn texts_at(ctx: &egui::Context, app: &mut VectorcraftApp, w: f32, h: f32) -> Vec<(String, egui::Rect)> { + fn collect(s: &egui::Shape, out: &mut Vec<(String, egui::Rect)>) { + match s { + egui::Shape::Text(t) => out.push((t.galley.text().to_string(), t.visual_bounding_rect())), + egui::Shape::Vec(v) => v.iter().for_each(|s| collect(s, out)), + _ => {} + } + } + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(w, h)); + let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| super::super::show(app, ui.ctx())); + out.textures_delta.clear(); + let mut texts = vec![]; + for c in &out.shapes { + collect(&c.shape, &mut texts); + } + texts +} + +/// Going from a shorter section to a longer one keeps OK and Cancel on a small screen in the first +/// frame after the switch, not only once the window has measured itself again. +#[test] +fn switching_to_a_long_section_keeps_the_buttons_on_a_small_screen() { + let mut app = app(); + let mut attrs: Value = serde_json::from_str(KEPT).unwrap(); + for (key, value) in [ + ("right_indent", json!(4.0)), + ("first_line_indent", json!(12.0)), + ("space_after", json!(6.0)), + ("composer", json!("singleLine")), + ("roman_hanging", json!(true)), + ("hyphenate", json!(true)), + ("justification", json!({"auto_leading": 150.0})), + ("korean_line_break_anywhere", json!(true)), + ] { + attrs[key] = value; + } + app.run("paraStyle.new", json!({"name": "Body", "attrs": attrs})).unwrap(); + app.run("ui.paraStyleOptions", json!({"name": "Body"})).unwrap(); + for h in [500.0, 560.0, 640.0, 720.0] { + for to in SECTIONS { + for from in SECTIONS.into_iter().filter(|s| *s != to) { + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + set(&mut app, "section", json!(from)); + for _ in 0..5 { + let _ = texts_at(&ctx, &mut app, 900.0, h); + } + set(&mut app, "section", json!(to)); + let texts = texts_at(&ctx, &mut app, 900.0, h); + for label in ["OK", "Cancel"] { + let r = texts.iter().find(|(text, _)| text == label).unwrap_or_else(|| panic!("{label} is drawn")).1; + assert!(r.bottom() <= h + 1.0, "{label} stays on a {h}-point screen going from {from} to {to}: {r:?}"); + } + } + } + } +}