Text styles: Paragraph Style Options keeps OK and Cancel on a small screen after a section switch

The dialog stored the scroll area's offered height as its content
height, which is taller than a short section draws. The room it gave a
longer section after a switch then left too little for the rest of the
window, and on a small screen OK and Cancel went below the screen. It now
stores the height the scroll area shows, the smaller of the offered
height and the content's height.

A changed content height also lays the frame out again before it is
shown, so the window is placed at its new size in the frame of the
switch rather than one frame later.
This commit is contained in:
dotCooCoo
2026-11-12 21:34:46 -08:00
parent f062dd7ed2
commit fc48d84cad
2 changed files with 66 additions and 5 deletions
@@ -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::<f32>(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) {
@@ -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:?}");
}
}
}
}
}