Fix #31: the Save Changes dialog fits its content and the window at every UI scale
The Save / Don't Save / Cancel prompt (and every dialog in the shared
dialog frame) is as tall as its text and buttons at every UI Scaling value,
including when the scale changes while it is open, and never wider than the
window: at 2x in a small window the question wraps instead of running off
the left edge.
Root cause: the tall, mostly empty prompt in the report was the shared
frame's button row taking all the window's height, fixed on main by
d3df1bce (M14.1). What remained was the width: the frame's minimum and
maximum widths are in points, and at a large UI scale a small window has
fewer points than the dialog's 466 pt, so it overflowed the window. The
frame now caps both widths to the window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
25a194cc00
commit
a52c030963
|
|
@ -91,6 +91,10 @@ use crate::{VectorcraftApp, widgets};
|
|||
|
||||
type DialogResult = Result<Value, String>;
|
||||
|
||||
/// The shared dialog frame's inner margin, and the least room it leaves at the window's edges.
|
||||
const MARGIN: i8 = 22;
|
||||
const EDGE_GAP: f32 = 8.0;
|
||||
|
||||
/// How a dialog draws and applies itself. Specs start from [`DialogSpec::FORM`] and override what
|
||||
/// differs.
|
||||
pub(crate) struct DialogSpec {
|
||||
|
|
@ -298,12 +302,12 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
|||
.resizable(false)
|
||||
.title_bar(false)
|
||||
.anchor(egui::Align2::CENTER_CENTER, [0.0, -40.0])
|
||||
.frame(egui::Frame::window(&ctx.global_style()).fill(t.panel).inner_margin(egui::Margin::same(22)))
|
||||
.frame(egui::Frame::window(&ctx.global_style()).fill(t.panel).inner_margin(egui::Margin::same(MARGIN)))
|
||||
.show(ctx, |ui| {
|
||||
ui.set_min_width(spec.min_width);
|
||||
if let Some(w) = spec.max_width {
|
||||
ui.set_max_width(w);
|
||||
}
|
||||
// Never wider than the window (a large UI scale in a small window): the text wraps.
|
||||
let room = (ctx.content_rect().width() - 2.0 * (f32::from(MARGIN) + EDGE_GAP)).max(EDGE_GAP);
|
||||
ui.set_min_width(spec.min_width.min(room));
|
||||
ui.set_max_width(spec.max_width.map_or(room, |w| w.min(room)));
|
||||
ui.label(egui::RichText::new(heading.as_str()).font(theme::semibold(16.0)).color(t.text));
|
||||
ui.add_space(12.0);
|
||||
cancel = (spec.body)(app, ui, &mut d);
|
||||
|
|
@ -383,3 +387,6 @@ mod tests_print_presets;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests_print_advanced;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_scale;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
//! The shared dialog frame at every UI scale (Preferences → User Interface → UI Scaling zooms the
|
||||
//! whole interface, so a window holds fewer points).
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The save prompt's rect after a few frames in a window of `physical` pixels at UI scale `zoom`.
|
||||
fn prompt(app: &mut VectorcraftApp, ctx: &egui::Context, physical: egui::Vec2, zoom: f32) -> (egui::Rect, egui::Rect) {
|
||||
ctx.set_zoom_factor(zoom);
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, physical / zoom);
|
||||
for _ in 0..10 {
|
||||
let input = egui::RawInput { screen_rect: Some(screen), ..Default::default() };
|
||||
ctx.run_ui(input, |ui| show(app, ui.ctx())).textures_delta.clear();
|
||||
}
|
||||
let rect = ctx.memory(|m| m.area_rect(egui::Id::new(("dialog", crate::unsaved::KIND)))).unwrap();
|
||||
(rect, screen)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_save_prompt_fits_its_content_and_the_window_at_every_ui_scale() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
theme::install_fonts(&ctx);
|
||||
theme::apply(&ctx, Default::default());
|
||||
for name in ["Untitled-1", "Quarterly campaign poster, final revision with the printer's notes"] {
|
||||
app.ui.dialog = Some(Dialog::new(crate::unsaved::KIND, json!({"index": 0, "name": name, "then": "close"})));
|
||||
// Scaled up and down while the prompt is open, in a full-size and in the smallest window.
|
||||
for (w, h, zoom) in
|
||||
[(1440.0, 900.0, 1.0), (1440.0, 900.0, 2.0), (1440.0, 900.0, 1.5), (800.0, 500.0, 2.0), (800.0, 500.0, 1.0), (1440.0, 900.0, 0.75)]
|
||||
{
|
||||
let (rect, screen) = prompt(&mut app, &ctx, egui::vec2(w, h), zoom);
|
||||
let at = format!("{name}: {w} × {h} px at {zoom}×: {rect:?} in {screen:?}");
|
||||
assert!(screen.contains_rect(rect), "inside the window, {at}");
|
||||
// As tall as its text and buttons (no empty band around the buttons).
|
||||
assert!(rect.height() < 200.0, "{at}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue