Merge PR #757 from switch87: Preferences: Cancel, Esc and the close button undo the dialog's changes (#752)

This commit is contained in:
Brandon Thomas
2026-10-14 21:59:38 -04:00
2 changed files with 103 additions and 2 deletions
+38 -2
View File
@@ -108,6 +108,9 @@ pub fn show(app: &mut SlideApp, ctx: &egui::Context) {
let escape = ctx.input(|i| i.key_pressed(egui::Key::Escape));
if open && !close && !escape {
app.dialog = Some(d);
} else if !close && d.id == "preferences" {
// Esc or the window's close button: the same as Cancel.
restore_preferences(app, &d);
} else if app.dialog.is_none()
&& let Some(ret) = d.params.get("returnParams").filter(|v| v.is_object()).cloned()
{
@@ -137,6 +140,31 @@ fn buttons(ui: &mut Ui, ok_label: &str) -> (bool, bool) {
(ok, cancel)
}
/// What the Preferences dialog can change, as it was when the dialog opened.
fn preferences_snapshot(app: &SlideApp) -> Value {
json!({
"dark": app.ui.brightness == theme::Brightness::Dark,
"uiScale": app.ui.ui_scale,
"prefs": serde_json::to_value(&app.session.prefs).unwrap_or(Value::Null),
})
}
/// Puts back the settings the Preferences dialog opened with (Cancel, Esc, the close button).
fn restore_preferences(app: &mut SlideApp, d: &Dialog) {
let Some(before) = d.params.get("before") else { return };
if let Some(dark) = before.get("dark").and_then(Value::as_bool)
&& dark != (app.ui.brightness == theme::Brightness::Dark)
{
let _ = app.run("view.dark", json!({ "on": dark }));
}
if let Some(scale) = before.get("uiScale").and_then(Value::as_f64) {
app.ui.ui_scale = scale as f32;
}
if let Some(prefs) = before.get("prefs").and_then(|p| serde_json::from_value(p.clone()).ok()) {
app.session.prefs = prefs;
}
}
fn field(ui: &mut Ui, d: &mut Dialog, label: &str, key: &str, default: &str) {
let mut v = d.get(key, default);
ui.horizontal(|ui| {
@@ -2104,6 +2132,11 @@ fn body(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
}
"gridOptions" => grid_options(app, ui, d),
"preferences" => {
// The controls apply as they change, so the dialog remembers what it opened with and
// Cancel (or Esc, or the close button: see `show`) puts that back.
if d.params.get("before").is_none() {
d.params = json!({ "before": preferences_snapshot(app) });
}
ui.label(egui::RichText::new("General").font(theme::bold(13.0)));
let mut dark = app.ui.brightness == theme::Brightness::Dark;
if ui.checkbox(&mut dark, "Dark appearance").changed() {
@@ -2130,8 +2163,11 @@ fn body(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
ui.label("Your name (comments):");
ui.text_edit_singleline(&mut app.session.prefs.author);
});
let (ok, _) = buttons(ui, "Done");
ok
let (ok, cancel) = buttons(ui, "Done");
if cancel {
restore_preferences(app, d);
}
ok || cancel
}
"photoAlbum" => photo_album_dialog(app, ui, d),
"themeColors" => theme_colors_dialog(app, ui, d),
+65
View File
@@ -2573,6 +2573,71 @@ mod tests {
assert!((harness.state().session.prefs.grid_spacing - 6.0).abs() < 1e-9);
}
/// Preferences (#752): Cancel, Esc and the window's close button put back what the dialog
/// changed while it was open; Done keeps it.
#[test]
fn preferences_cancel_restores_the_settings_done_keeps_them() {
use egui_kittest::kittest::Queryable;
let mut harness = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SlideApp| {
app.logic(ui.ctx());
app.ui(ui);
},
SlideApp::new(Session::with_new(), Services::default()),
);
harness.run_steps(2);
let before = harness.state().session.prefs.clone();
let dark_before = harness.state().ui.brightness;
let change = |harness: &mut egui_kittest::Harness<'static, SlideApp>| {
harness.state_mut().run("app.preferences", json!({})).unwrap();
harness.run_steps(2);
for label in ["Dark appearance", "Show smart guides", "Snap objects to grid"] {
harness
.query_all_by_role_and_label(egui::accesskit::Role::CheckBox, label)
.next()
.unwrap_or_else(|| panic!("no {label} checkbox"))
.click_accesskit();
harness.run_steps(2);
}
assert_ne!(harness.state().ui.brightness, dark_before, "the dialog previews the appearance");
assert_ne!(harness.state().session.prefs.smart_guides, before.smart_guides);
};
let unchanged = |harness: &egui_kittest::Harness<'static, SlideApp>, how: &str| {
let s = harness.state();
assert!(s.dialog.is_none(), "{how} closes Preferences");
assert_eq!(s.ui.brightness, dark_before, "{how} restores the appearance");
assert_eq!(s.session.prefs.smart_guides, before.smart_guides, "{how} restores smart guides");
assert_eq!(s.session.prefs.snap_to_grid, before.snap_to_grid, "{how} restores snap to grid");
};
change(&mut harness);
harness
.query_all_by_role_and_label(egui::accesskit::Role::Button, "Cancel")
.next()
.unwrap_or_else(|| panic!("no Cancel button"))
.click_accesskit();
harness.run_steps(2);
unchanged(&harness, "Cancel");
change(&mut harness);
harness.key_press(egui::Key::Escape);
harness.run_steps(2);
unchanged(&harness, "Esc");
change(&mut harness);
harness
.query_all_by_role_and_label(egui::accesskit::Role::Button, "Done")
.next()
.unwrap_or_else(|| panic!("no Done button"))
.click_accesskit();
harness.run_steps(2);
let s = harness.state();
assert!(s.dialog.is_none(), "Done closes Preferences");
assert_ne!(s.ui.brightness, dark_before, "Done keeps the appearance");
assert_ne!(s.session.prefs.smart_guides, before.smart_guides, "Done keeps smart guides");
assert_ne!(s.session.prefs.snap_to_grid, before.snap_to_grid, "Done keeps snap to grid");
}
fn canvas_harness() -> egui_kittest::Harness<'static, SlideApp> {
let mut harness = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|ui, app: &mut SlideApp| {