diff --git a/crates/ui-egui/src/documents.rs b/crates/ui-egui/src/documents.rs index 20045986..e87c5d5d 100644 --- a/crates/ui-egui/src/documents.rs +++ b/crates/ui-egui/src/documents.rs @@ -48,6 +48,8 @@ pub struct Documents { pub closing: Option, /// Quitting: the tabs answered so far with Don't Save (`None`: not quitting). pub quitting: Option>, + /// The "save changes?" button the arrow keys have picked; Enter gives it. + pub choice: Answer, } impl Documents { @@ -458,8 +460,9 @@ pub fn layout(n: usize, active: usize, room: f32) -> (usize, usize, f32) { } /// What "save changes?" was answered. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Answer { + #[default] Save, DontSave, Cancel, @@ -473,6 +476,28 @@ pub fn prompt(app: &mut SolveApp, ctx: &egui::Context) { let name = tabs_info(app).get(i).map(|x| x.0.clone()).unwrap_or_default(); let name_display = if name == "Untitled" { crate::i18n::tr("ui.untitled") } else { &name }; let mut answer: Option = None; + // Left and Right move between the buttons (Save is picked first); Enter gives the picked one. + let order = [Answer::Save, Answer::DontSave, Answer::Cancel]; + let (left, right, enter) = ctx.input_mut(|x| { + ( + x.consume_key(egui::Modifiers::NONE, egui::Key::ArrowLeft), + x.consume_key(egui::Modifiers::NONE, egui::Key::ArrowRight), + x.consume_key(egui::Modifiers::NONE, egui::Key::Enter), + ) + }); + let at = order.iter().position(|a| *a == app.docs.choice).unwrap_or(0); + let at = if left { + at.saturating_sub(1) + } else if right { + (at + 1).min(order.len() - 1) + } else { + at + }; + let choice = order.get(at).copied().unwrap_or_default(); + app.docs.choice = choice; + if enter { + answer = Some(choice); + } let title = crate::i18n::format("document.save_changes", &[("name", name_display)]); crate::frame::window(ctx, title, crate::frame::Width::Normal) .id(egui::Id::new("sc_close_prompt")) @@ -483,13 +508,13 @@ pub fn prompt(app: &mut SolveApp, ctx: &egui::Context) { ui.label(crate::i18n::format("document.unsaved_changes", &[("name", name_display)])); ui.add_space(8.0); ui.horizontal(|ui| { - if ui.button(crate::i18n::tr("ui.save")).clicked() { + if ui.add(egui::Button::new(crate::i18n::tr("ui.save")).selected(choice == Answer::Save)).clicked() { answer = Some(Answer::Save); } - if ui.button(crate::i18n::tr("ui.don_t_save")).clicked() { + if ui.add(egui::Button::new(crate::i18n::tr("ui.don_t_save")).selected(choice == Answer::DontSave)).clicked() { answer = Some(Answer::DontSave); } - if ui.button(crate::i18n::tr("ui.cancel")).clicked() { + if ui.add(egui::Button::new(crate::i18n::tr("ui.cancel")).selected(choice == Answer::Cancel)).clicked() { answer = Some(Answer::Cancel); } }); @@ -506,6 +531,7 @@ pub fn prompt(app: &mut SolveApp, ctx: &egui::Context) { /// Act on the "save changes?" answer (also the control channel's `ui.documents {answer}`). pub fn answer_prompt(app: &mut SolveApp, answer: Answer) { let Some(i) = app.docs.closing else { return }; + app.docs.choice = Answer::Save; match answer { Answer::Save => { switch(app, i); diff --git a/crates/ui-egui/tests/scenarios/s43_quit_asks_to_save.json b/crates/ui-egui/tests/scenarios/s43_quit_asks_to_save.json index 08ce24f0..e3c7c0f3 100644 --- a/crates/ui-egui/tests/scenarios/s43_quit_asks_to_save.json +++ b/crates/ui-egui/tests/scenarios/s43_quit_asks_to_save.json @@ -10,7 +10,17 @@ {"expect": {"shown": "Don't Save", "quit_requested": false}}, {"key": "Escape"}, {"expect": {"not_shown": "Don't Save", "quitting": false, "quit_requested": false}}, + {"note": "Left and Right pick a button (Save first, stopping at the ends) and Enter gives it."}, {"call": "app.quit"}, - {"widget": "Don't Save"}, + {"key": "ArrowRight"}, + {"key": "ArrowRight"}, + {"key": "ArrowRight"}, + {"key": "Enter"}, + {"expect": {"not_shown": "Don't Save", "quitting": false, "quit_requested": false, "bodies": 1}}, + {"call": "app.quit"}, + {"key": "ArrowRight"}, + {"key": "ArrowRight"}, + {"key": "ArrowLeft"}, + {"key": "Enter"}, {"expect": {"quit_requested": true}} ]